Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/knockapi/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@
import anyio
import httpx
import distro
import pydantic
from httpx import URL
from pydantic import PrivateAttr

from . import _exceptions
from ._qs import Querystring
Expand All @@ -61,6 +59,7 @@
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
from ._compat import PYDANTIC_V1, model_copy, model_dump
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
from ._pydantic import PrivateAttr, ValidationError as _ValidationError
from ._response import (
APIResponse,
BaseAPIResponse,
Expand Down Expand Up @@ -224,7 +223,7 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:


class BaseSyncPage(BasePage[_T], Generic[_T]):
_client: SyncAPIClient = pydantic.PrivateAttr()
_client: SyncAPIClient = PrivateAttr()

def _set_private_attributes(
self,
Expand Down Expand Up @@ -312,7 +311,7 @@ async def __aiter__(self) -> AsyncIterator[_T]:


class BaseAsyncPage(BasePage[_T], Generic[_T]):
_client: AsyncAPIClient = pydantic.PrivateAttr()
_client: AsyncAPIClient = PrivateAttr()

def _set_private_attributes(
self,
Expand Down Expand Up @@ -626,7 +625,7 @@ def _process_response_data(
return cast(ResponseT, validate_type(type_=cast_to, value=data))

return cast(ResponseT, construct_type(type_=cast_to, value=data))
except pydantic.ValidationError as err:
except _ValidationError as err:
raise APIResponseValidationError(response=response, body=data) from err

@property
Expand Down
42 changes: 30 additions & 12 deletions src/knockapi/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Pyright incorrectly reports some of our functions as overriding a method when they don't
# pyright: reportIncompatibleMethodOverride=false

PYDANTIC_V1 = pydantic.VERSION.startswith("1.")
PYDANTIC_V1 = True

if TYPE_CHECKING:

Expand All @@ -43,16 +43,29 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001
...

else:
# v1 re-exports
# v1 re-exports — route through pydantic.v1 when v2 is installed
if PYDANTIC_V1:
from pydantic.typing import (
get_args as get_args,
is_union as is_union,
get_origin as get_origin,
is_typeddict as is_typeddict,
is_literal_type as is_literal_type,
)
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
if not pydantic.VERSION.startswith("1."):
from pydantic.v1.typing import ( # type: ignore[no-redef]
get_args as get_args,
is_union as is_union,
get_origin as get_origin,
is_typeddict as is_typeddict,
is_literal_type as is_literal_type,
)
from pydantic.v1.datetime_parse import ( # type: ignore[no-redef]
parse_date as parse_date,
parse_datetime as parse_datetime,
)
else:
from pydantic.typing import (
get_args as get_args,
is_union as is_union,
get_origin as get_origin,
is_typeddict as is_typeddict,
is_literal_type as is_literal_type,
)
from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
else:
from ._utils import (
get_args as get_args,
Expand Down Expand Up @@ -172,9 +185,14 @@ class GenericModel(pydantic.BaseModel): ...

else:
if PYDANTIC_V1:
import pydantic.generics
if not pydantic.VERSION.startswith("1."):
import pydantic.v1.generics # type: ignore[import-untyped]

class GenericModel(pydantic.v1.generics.GenericModel, pydantic.v1.BaseModel): ... # type: ignore[no-redef]
else:
import pydantic.generics

class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
else:
# there no longer needs to be a distinction in v2 but
# we still have to create our own subclass to avoid
Expand Down
23 changes: 18 additions & 5 deletions src/knockapi/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
if TYPE_CHECKING:
from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema

_PydanticV1BaseModel = pydantic.BaseModel
else:
from ._pydantic import BaseModel as _PydanticV1BaseModel

__all__ = ["BaseModel", "GenericModel"]

_T = TypeVar("_T")
Expand All @@ -80,7 +84,7 @@ class _ConfigProtocol(Protocol):
allow_population_by_field_name: bool


class BaseModel(pydantic.BaseModel):
class BaseModel(_PydanticV1BaseModel): # type: ignore[misc]
if PYDANTIC_V1:

@property
Expand All @@ -89,8 +93,8 @@ def model_fields_set(self) -> set[str]:
# a forwards-compat shim for pydantic v2
return self.__fields_set__ # type: ignore

class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated]
extra: Any = pydantic.Extra.allow # type: ignore
class Config(_PydanticV1BaseModel.Config): # type: ignore[misc, name-defined]
extra = "allow" # type: ignore
else:
model_config: ClassVar[ConfigDict] = ConfigDict(
extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
Expand Down Expand Up @@ -260,6 +264,7 @@ def model_dump(
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
Expand Down Expand Up @@ -288,6 +293,8 @@ def model_dump(
"""
if mode not in {"json", "python"}:
raise ValueError("mode must be either 'json' or 'python'")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
if round_trip != False:
raise ValueError("round_trip is only supported in Pydantic v2")
if warnings != True:
Expand All @@ -314,12 +321,14 @@ def model_dump_json(
self,
*,
indent: int | None = None,
ensure_ascii: bool = False,
include: IncEx | None = None,
exclude: IncEx | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
Expand All @@ -344,6 +353,10 @@ def model_dump_json(
Returns:
A JSON string representation of the model.
"""
if ensure_ascii != False:
raise ValueError("ensure_ascii is only supported in Pydantic v2")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
if round_trip != False:
raise ValueError("round_trip is only supported in Pydantic v2")
if warnings != True:
Expand Down Expand Up @@ -771,7 +784,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):


@final
class FinalRequestOptions(pydantic.BaseModel):
class FinalRequestOptions(_PydanticV1BaseModel): # type: ignore[misc]
method: str
url: str
params: Query = {}
Expand All @@ -790,7 +803,7 @@ class FinalRequestOptions(pydantic.BaseModel):

if PYDANTIC_V1:

class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated]
class Config(_PydanticV1BaseModel.Config): # type: ignore[misc, name-defined]
arbitrary_types_allowed: bool = True
else:
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
Expand Down
18 changes: 18 additions & 0 deletions src/knockapi/_pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Central pydantic compatibility shim.

When pydantic v2 is installed, import from the bundled v1 compat layer
(pydantic.v1). When pydantic v1 is installed natively, import directly.
"""

from __future__ import annotations

import pydantic

if not pydantic.VERSION.startswith("1."):
from pydantic.v1 import Field, BaseModel, BaseConfig, PrivateAttr, ValidationError # type: ignore[no-redef]
from pydantic.v1.fields import FieldInfo # type: ignore[no-redef, assignment]
else:
from pydantic import Field, BaseModel, BaseConfig, PrivateAttr, ValidationError # type: ignore[assignment]
from pydantic.fields import FieldInfo # type: ignore[assignment]

__all__ = ["BaseConfig", "BaseModel", "Field", "FieldInfo", "PrivateAttr", "ValidationError"]
6 changes: 5 additions & 1 deletion src/knockapi/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@
from ._exceptions import KnockError, APIResponseValidationError

if TYPE_CHECKING:
_PydanticBaseModel = pydantic.BaseModel

from ._models import FinalRequestOptions
from ._base_client import BaseClient
else:
from ._pydantic import BaseModel as _PydanticBaseModel


P = ParamSpec("P")
Expand Down Expand Up @@ -215,7 +219,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
origin # pyright: ignore[reportUnknownArgumentType]
)
and not issubclass(origin, BaseModel)
and issubclass(origin, pydantic.BaseModel)
and (issubclass(origin, _PydanticBaseModel) or issubclass(origin, pydantic.BaseModel))
):
raise TypeError("Pydantic models must subclass our base model type, e.g. `from knockapi import BaseModel`")

Expand Down
11 changes: 8 additions & 3 deletions src/knockapi/_utils/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
import io
import base64
import pathlib
from typing import Any, Mapping, TypeVar, cast
from typing import TYPE_CHECKING, Any, Mapping, TypeVar, cast
from datetime import date, datetime
from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints

import anyio
import pydantic

if TYPE_CHECKING:
_PydanticBaseModel = pydantic.BaseModel
else:
from .._pydantic import BaseModel as _PydanticBaseModel

from ._utils import (
is_list,
is_given,
Expand Down Expand Up @@ -217,7 +222,7 @@ def _transform_recursive(
data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
return data

if isinstance(data, pydantic.BaseModel):
if isinstance(data, _PydanticBaseModel):
return model_dump(data, exclude_unset=True, mode="json")

annotated_type = _get_annotated_type(annotation)
Expand Down Expand Up @@ -383,7 +388,7 @@ async def _async_transform_recursive(
data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
return data

if isinstance(data, pydantic.BaseModel):
if isinstance(data, _PydanticBaseModel):
return model_dump(data, exclude_unset=True, mode="json")

annotated_type = _get_annotated_type(annotation)
Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from typing import Dict, Optional
from datetime import datetime

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .recipient import Recipient
from .._pydantic import Field as FieldInfo

__all__ = ["Activity"]

Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/audience_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from typing import Optional
from datetime import datetime

from pydantic import Field as FieldInfo

from .user import User
from .._models import BaseModel
from .._pydantic import Field as FieldInfo

__all__ = ["AudienceMember"]

Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/bulk_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from datetime import datetime
from typing_extensions import Literal

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo

__all__ = ["BulkOperation", "ErrorItem"]

Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from datetime import datetime
from typing_extensions import Literal

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo
from .recipient_reference import RecipientReference

__all__ = ["Message", "Source", "Channel"]
Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/message_delivery_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from typing import Dict, Union, Optional
from typing_extensions import Literal

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo

__all__ = ["MessageDeliveryLog", "Request", "Response"]

Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/message_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from datetime import datetime
from typing_extensions import Literal

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo
from .recipient_reference import RecipientReference

__all__ = ["MessageEvent"]
Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/message_get_content_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from datetime import datetime
from typing_extensions import Literal, TypeAlias

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo

__all__ = [
"MessageGetContentResponse",
Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/messages/batch_get_content_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from datetime import datetime
from typing_extensions import Literal, TypeAlias

from pydantic import Field as FieldInfo

from ..._models import BaseModel
from ..._pydantic import Field as FieldInfo

__all__ = [
"BatchGetContentResponse",
Expand Down
3 changes: 1 addition & 2 deletions src/knockapi/types/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from typing import Dict, Optional
from datetime import datetime

from pydantic import Field as FieldInfo

from .._models import BaseModel
from .._pydantic import Field as FieldInfo

__all__ = ["Object"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

from typing import List, Optional

from pydantic import Field as FieldInfo

from ..._models import BaseModel
from ..._pydantic import Field as FieldInfo

__all__ = ["MsTeamListChannelsResponse", "MsTeamsChannel"]

Expand Down
Loading
Loading