-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from surenkov/feature/pydantic-v2
Pydantic 2 support
- Loading branch information
Showing
68 changed files
with
4,018 additions
and
914 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
indent_style = space | ||
indent_size = 4 | ||
|
||
[*.py] | ||
max_line_length = 120 | ||
|
||
[Makefile] | ||
indent_style = tab | ||
indent_size = 4 | ||
|
||
|
||
[{*.json,*.yml,*.yaml}] | ||
indent_size = 2 | ||
insert_final_newline = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,5 @@ dist/ | |
*.egg-info/ | ||
build | ||
htmlcov | ||
|
||
.python-version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,28 @@ | ||
.PHONY: install build test lint upload upload-test clean | ||
|
||
.PHONY: install | ||
install: | ||
python3 -m pip install build twine | ||
python3 -m pip install -e .[dev,test] | ||
|
||
|
||
.PHONY: build | ||
build: | ||
python3 -m build | ||
|
||
migrations: | ||
DJANGO_SETTINGS_MODULE="tests.settings.django_test_settings" python3 -m django makemigrations --noinput | ||
|
||
.PHONY: test | ||
test: A= | ||
test: | ||
pytest $(A) | ||
|
||
.PHONY: lint | ||
lint: A=. | ||
lint: | ||
mypy $(A) | ||
|
||
|
||
.PHONY: upload | ||
upload: | ||
python3 -m twine upload dist/* | ||
|
||
|
||
.PHONY: upload-test | ||
upload-test: | ||
python3 -m twine upload --repository testpypi dist/* | ||
|
||
|
||
.PHONY: clean | ||
clean: | ||
rm -rf dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
from .fields import * | ||
from .fields import SchemaField as SchemaField | ||
|
||
def __getattr__(name): | ||
if name == "_migration_serializers": | ||
module = __import__("django_pydantic_field._migration_serializers", fromlist=["*"]) | ||
return module | ||
|
||
raise AttributeError(f"Module {__name__!r} has no attribute {name!r}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,144 +1,9 @@ | ||
""" | ||
Django Migration serializer helpers | ||
[Built-in generic annotations](https://peps.python.org/pep-0585/) | ||
introduced in Python 3.9 are having a different semantics from `typing` collections. | ||
Due to how Django treats field serialization/reconstruction while writing migrations, | ||
it is not possible to distnguish between `types.GenericAlias` and any other regular types, | ||
thus annotations are being erased by `MigrationWriter` serializers. | ||
To mitigate this, I had to introduce custom container for schema deconstruction. | ||
[Union types syntax](https://peps.python.org/pep-0604/) | ||
`typing.Union` and its special forms, like `typing.Optional`, have its own inheritance chain. | ||
Moreover, `types.UnionType`, introduced in 3.10, do not allow explicit type construction, | ||
only with `X | Y` syntax. Both cases require a dedicated serializer for migration writes. | ||
""" | ||
import sys | ||
import types | ||
import typing as t | ||
|
||
try: | ||
from typing import get_args, get_origin | ||
except ImportError: | ||
from typing_extensions import get_args, get_origin | ||
|
||
from django.db.migrations.serializer import BaseSerializer, serializer_factory | ||
from django.db.migrations.writer import MigrationWriter | ||
|
||
|
||
class GenericContainer: | ||
__slots__ = "origin", "args" | ||
|
||
def __init__(self, origin, args: tuple = ()): | ||
self.origin = origin | ||
self.args = args | ||
|
||
@classmethod | ||
def wrap(cls, typ_): | ||
if isinstance(typ_, GenericTypes): | ||
wrapped_args = tuple(map(cls.wrap, get_args(typ_))) | ||
return cls(get_origin(typ_), wrapped_args) | ||
return typ_ | ||
|
||
@classmethod | ||
def unwrap(cls, type_): | ||
if not isinstance(type_, GenericContainer): | ||
return type_ | ||
|
||
if not type_.args: | ||
return type_.origin | ||
|
||
unwrapped_args = tuple(map(cls.unwrap, type_.args)) | ||
try: | ||
# This is a fallback for Python < 3.8, please be careful with that | ||
return type_.origin[unwrapped_args] | ||
except TypeError: | ||
return GenericAlias(type_.origin, unwrapped_args) | ||
|
||
def __repr__(self): | ||
return repr(self.unwrap(self)) | ||
|
||
__str__ = __repr__ | ||
|
||
def __eq__(self, other): | ||
if isinstance(other, self.__class__): | ||
return self.origin == other.origin and self.args == other.args | ||
if isinstance(other, GenericTypes): | ||
return self == self.wrap(other) | ||
return NotImplemented | ||
|
||
|
||
class GenericSerializer(BaseSerializer): | ||
value: GenericContainer | ||
|
||
def serialize(self): | ||
value = self.value | ||
|
||
tp_repr, imports = serializer_factory(type(value)).serialize() | ||
orig_repr, orig_imports = serializer_factory(value.origin).serialize() | ||
imports.update(orig_imports) | ||
|
||
args = [] | ||
for arg in value.args: | ||
arg_repr, arg_imports = serializer_factory(arg).serialize() | ||
args.append(arg_repr) | ||
imports.update(arg_imports) | ||
|
||
if args: | ||
args_repr = ", ".join(args) | ||
generic_repr = "%s(%s, (%s,))" % (tp_repr, orig_repr, args_repr) | ||
else: | ||
generic_repr = "%s(%s)" % (tp_repr, orig_repr) | ||
|
||
return generic_repr, imports | ||
|
||
|
||
class TypingSerializer(BaseSerializer): | ||
def serialize(self): | ||
orig_module = self.value.__module__ | ||
orig_repr = repr(self.value) | ||
|
||
if not orig_repr.startswith(orig_module): | ||
orig_repr = f"{orig_module}.{orig_repr}" | ||
|
||
return orig_repr, {f"import {orig_module}"} | ||
|
||
|
||
if sys.version_info >= (3, 9): | ||
GenericAlias = types.GenericAlias | ||
GenericTypes: t.Tuple[t.Any, ...] = ( | ||
GenericAlias, | ||
type(t.List[int]), | ||
type(t.List), | ||
) | ||
else: | ||
# types.GenericAlias is missing, meaning python version < 3.9, | ||
# which has a different inheritance models for typed generics | ||
GenericAlias = type(t.List[int]) | ||
GenericTypes = GenericAlias, type(t.List) | ||
|
||
|
||
MigrationWriter.register_serializer(GenericContainer, GenericSerializer) | ||
MigrationWriter.register_serializer(t.ForwardRef, TypingSerializer) | ||
MigrationWriter.register_serializer(type(t.Union), TypingSerializer) # type: ignore | ||
|
||
|
||
if sys.version_info >= (3, 10): | ||
UnionType = types.UnionType | ||
|
||
class UnionTypeSerializer(BaseSerializer): | ||
value: UnionType | ||
|
||
def serialize(self): | ||
imports = set() | ||
if isinstance(self.value, type(t.Union)): # type: ignore | ||
imports.add("import typing") | ||
|
||
for arg in get_args(self.value): | ||
_, arg_imports = serializer_factory(arg).serialize() | ||
imports.update(arg_imports) | ||
|
||
return repr(self.value), imports | ||
|
||
MigrationWriter.register_serializer(UnionType, UnionTypeSerializer) | ||
import warnings | ||
from .compat.django import * | ||
|
||
DEPRECATION_MSG = ( | ||
"Module 'django_pydantic_field._migration_serializers' is deprecated " | ||
"and will be removed in version 1.0.0. " | ||
"Please replace it with 'django_pydantic_field.compat.django' in migrations." | ||
) | ||
warnings.warn(DEPRECATION_MSG, category=DeprecationWarning) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .pydantic import PYDANTIC_V1 as PYDANTIC_V1 | ||
from .pydantic import PYDANTIC_V2 as PYDANTIC_V2 | ||
from .pydantic import PYDANTIC_VERSION as PYDANTIC_VERSION | ||
from .django import GenericContainer as GenericContainer | ||
from .django import MigrationWriter as MigrationWriter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from __future__ import annotations | ||
|
||
import typing as ty | ||
import warnings | ||
|
||
_MISSING = object() | ||
_DEPRECATED_KWARGS = ( | ||
"allow_nan", | ||
"indent", | ||
"separators", | ||
"skipkeys", | ||
"sort_keys", | ||
) | ||
_DEPRECATED_KWARGS_MESSAGE = ( | ||
"The `%s=` argument is not supported by Pydantic v2 and will be removed in the future versions." | ||
) | ||
|
||
|
||
def truncate_deprecated_v1_export_kwargs(kwargs: dict[str, ty.Any]) -> None: | ||
for kwarg in _DEPRECATED_KWARGS: | ||
maybe_present_kwarg = kwargs.pop(kwarg, _MISSING) | ||
if maybe_present_kwarg is not _MISSING: | ||
warnings.warn(_DEPRECATED_KWARGS_MESSAGE % (kwarg,), DeprecationWarning, stacklevel=2) |
Oops, something went wrong.