Skip to content

Commit

Permalink
feat: ✨ Drop Python 3.9 support, add 3.13 support and update things
Browse files Browse the repository at this point in the history
  • Loading branch information
ddanier committed Nov 7, 2024
1 parent 23d45fe commit d0c4038
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 56 deletions.
12 changes: 4 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: check-added-large-files
- id: check-merge-conflict
- id: check-docstring-first
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.1
rev: v0.7.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.3.3
hooks:
- id: python-safety-dependencies-check
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.11.0
rev: v9.18.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies:
- "@commitlint/config-conventional"
default_stages:
- commit
- pre-commit
default_install_hook_types:
- pre-commit
- commit-msg
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Just use `pip install pydantic-changedetect` to install the library.

**Note:** `pydantic-changedetect` is compatible with `pydantic` versions `1.9`, `1.10` and even `2.x` (🥳) on
Python `3.8`, `3.9`, `3.10`, `3.11` and `3.12`. This is also ensured running all tests on all those versions
Python `3.9`, `3.10`, `3.11`, `3.12` and `3.13`. This is also ensured running all tests on all those versions
using `tox`.

## About
Expand Down
8 changes: 4 additions & 4 deletions pydantic_changedetect/_compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Type
from typing import Any

import pydantic
from pydantic.fields import FieldInfo
Expand All @@ -19,7 +19,7 @@ def __init__(
self.obj = obj

@property
def model_fields(self) -> Dict[str, FieldInfo]:
def model_fields(self) -> dict[str, FieldInfo]:
return self.obj.__fields__

def get_model_field_info_annotation(self, model_field: FieldInfo) -> type:
Expand All @@ -36,10 +36,10 @@ def __init__(
self.obj = obj

@property
def model_fields(self) -> Dict[str, FieldInfo]:
def model_fields(self) -> dict[str, FieldInfo]:
return self.obj.model_fields

def get_model_field_info_annotation(self, model_field: FieldInfo) -> Type[Any]:
def get_model_field_info_annotation(self, model_field: FieldInfo) -> type[Any]:
if model_field.annotation is None:
raise RuntimeError("model field has not typing annotation")
return model_field.annotation
20 changes: 9 additions & 11 deletions pydantic_changedetect/changedetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
TYPE_CHECKING,
Any,
Callable,
Dict,
Dict, # We still need to use this, as dict is a class method and pollutes the class scope
Literal,
Optional,
Set,
Type,
TypeVar,
Union,
cast,
Expand Down Expand Up @@ -72,7 +70,7 @@ class Something(ChangeDetectionMixin, pydantic.BaseModel):
# thus just new attributes the class has - not something you need to pass
# anywhere.
model_original: Dict[str, Any] = PrivateAttr(...)
model_self_changed_fields: Set[str] = PrivateAttr(...)
model_self_changed_fields: set[str] = PrivateAttr(...)
model_changed_markers: set[str] = PrivateAttr(...)

__slots__ = ("model_original", "model_self_changed_fields", "model_changed_markers")
Expand All @@ -92,7 +90,7 @@ def model_reset_changed(self) -> None:
object.__setattr__(self, "model_changed_markers", set())

@property
def model_changed_fields(self) -> Set[str]:
def model_changed_fields(self) -> set[str]:
"""Return list of all changed fields, submodels are considered as one field"""

self_compat = PydanticCompat(self)
Expand Down Expand Up @@ -141,7 +139,7 @@ def model_changed_fields(self) -> Set[str]:
return changed_fields

@property
def model_changed_fields_recursive(self) -> Set[str]:
def model_changed_fields_recursive(self) -> set[str]:
"""Return a list of all changed fields recursive using dotted syntax"""

self_compat = PydanticCompat(self)
Expand Down Expand Up @@ -437,7 +435,7 @@ def model_has_changed_marker(

if PYDANTIC_V2:
@classmethod
def model_construct(cls: Type["Model"], *args: Any, **kwargs: Any) -> "Model":
def model_construct(cls: type["Model"], *args: Any, **kwargs: Any) -> "Model":
"""Construct an unvalidated instance"""

m = cast("Model", super().model_construct(*args, **kwargs))
Expand Down Expand Up @@ -715,7 +713,7 @@ def json( # type: ignore

if PYDANTIC_V1: # pragma: no cover
@classmethod
def construct(cls: Type["Model"], *args: Any, **kwargs: Any) -> "Model":
def construct(cls: type["Model"], *args: Any, **kwargs: Any) -> "Model":
"""Construct an unvalidated instance"""

m = cast("Model", super().construct(*args, **kwargs))
Expand Down Expand Up @@ -831,7 +829,7 @@ def __original__(self) -> Dict[str, Any]:
return self.model_original

@property
def __self_changed_fields__(self) -> Set[str]:
def __self_changed_fields__(self) -> set[str]:
warnings.warn(
"__self_changed_fields__ is deprecated, use model_self_changed_fields instead",
DeprecationWarning,
Expand All @@ -840,7 +838,7 @@ def __self_changed_fields__(self) -> Set[str]:
return self.model_self_changed_fields

@property
def __changed_fields__(self) -> Set[str]:
def __changed_fields__(self) -> set[str]:
warnings.warn(
"__changed_fields__ is deprecated, use model_changed_fields instead",
DeprecationWarning,
Expand All @@ -849,7 +847,7 @@ def __changed_fields__(self) -> Set[str]:
return self.model_changed_fields

@property
def __changed_fields_recursive__(self) -> Set[str]:
def __changed_fields_recursive__(self) -> set[str]:
warnings.warn(
"__changed_fields_recursive__ is deprecated, use model_changed_fields_recursive instead",
DeprecationWarning,
Expand Down
13 changes: 10 additions & 3 deletions pydantic_changedetect/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import warnings
from typing import Any, Dict, List, Mapping, Set, Tuple, Type, Union, get_args, get_origin
from collections.abc import Mapping
from typing import Any, Dict, List, Set, Tuple, Union, get_args, get_origin

import pydantic_changedetect

Expand Down Expand Up @@ -38,14 +39,20 @@ def safe_issubclass(cls: Any, type_: Any) -> bool:
return False


def is_pydantic_change_detect_annotation(annotation: Type[Any]) -> bool:
def is_class_type(annotation: Any) -> bool:
# If the origin is None, it's likely a concrete class
return get_origin(annotation) is None


def is_pydantic_change_detect_annotation(annotation: type[Any]) -> bool:
"""
Return True if the given annotation is a ChangeDetectionMixin annotation.
"""

# if annotation is an ChangeDetectionMixin everything is easy
if (
isinstance(annotation, type)
is_class_type(annotation)
and isinstance(annotation, type)
and issubclass(annotation, pydantic_changedetect.ChangeDetectionMixin)
):
return True
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository = "https://github.com/team23/pydantic-changedetect"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"
pydantic = ">=1.9.0,<3.0.0"

[tool.poetry.group.dev.dependencies]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_changedetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class Nested(ChangeDetectionMixin, pydantic.BaseModel):


class NestedList(ChangeDetectionMixin, pydantic.BaseModel):
sub: List[Something]
sub: list[Something]


class NestedTuple(ChangeDetectionMixin, pydantic.BaseModel):
sub: Tuple[Something, ...]
sub: tuple[Something, ...]


class NestedDict(ChangeDetectionMixin, pydantic.BaseModel):
sub: Dict[str, Something]
sub: dict[str, Something]


class NestedUnsupported(ChangeDetectionMixin, pydantic.BaseModel):
Expand All @@ -48,7 +48,7 @@ class NestedWithDefault(ChangeDetectionMixin, pydantic.BaseModel):


class SomethingWithBrokenPickleState(Something):
def __getstate__(self) -> Dict[str, Any]:
def __getstate__(self) -> dict[str, Any]:
# Skip adding changed state in ChangedDetectionMixin.__getstate__
return super(ChangeDetectionMixin, self).__getstate__()

Expand Down
44 changes: 21 additions & 23 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@ def test_safe_issubclass():

def test_safe_issubclass_for_type_definitions():
with pytest.warns(DeprecationWarning):
assert safe_issubclass(List[str], BaseClass) is False
assert safe_issubclass(list[str], BaseClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(Dict[str, str], BaseClass) is False
assert safe_issubclass(dict[str, str], BaseClass) is False

if sys.version_info >= (3, 9):
with pytest.warns(DeprecationWarning):
assert safe_issubclass(list[str], BaseClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(dict[str, str], BaseClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(list[str], BaseClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(dict[str, str], BaseClass) is False


def test_ensure_normal_issubclass_raises_an_issue():
Expand All @@ -48,15 +47,14 @@ def test_ensure_normal_issubclass_raises_an_issue():

def test_safe_issubclass_for_type_definitions_for_abstract():
with pytest.warns(DeprecationWarning):
assert safe_issubclass(List[str], AbstractClass) is False
assert safe_issubclass(list[str], AbstractClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(Dict[str, str], AbstractClass) is False
assert safe_issubclass(dict[str, str], AbstractClass) is False

if sys.version_info >= (3, 9):
with pytest.warns(DeprecationWarning):
assert safe_issubclass(list[str], AbstractClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(dict[str, str], AbstractClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(list[str], AbstractClass) is False
with pytest.warns(DeprecationWarning):
assert safe_issubclass(dict[str, str], AbstractClass) is False


class SomeModel(ChangeDetectionMixin, pydantic.BaseModel):
Expand Down Expand Up @@ -95,17 +93,17 @@ def test_is_pydantic_change_detect_annotation_union_types():


def test_is_pydantic_change_detect_annotation_list_types():
assert is_pydantic_change_detect_annotation(List[int]) is False
assert is_pydantic_change_detect_annotation(List[OtherModel]) is False
assert is_pydantic_change_detect_annotation(Tuple[int]) is False
assert is_pydantic_change_detect_annotation(Tuple[OtherModel]) is False
assert is_pydantic_change_detect_annotation(list[int]) is False
assert is_pydantic_change_detect_annotation(list[OtherModel]) is False
assert is_pydantic_change_detect_annotation(tuple[int]) is False
assert is_pydantic_change_detect_annotation(tuple[OtherModel]) is False

assert is_pydantic_change_detect_annotation(List[SomeModel]) is True
assert is_pydantic_change_detect_annotation(Tuple[SomeModel]) is True
assert is_pydantic_change_detect_annotation(list[SomeModel]) is True
assert is_pydantic_change_detect_annotation(tuple[SomeModel]) is True


def test_is_pydantic_change_detect_annotation_dict_types():
assert is_pydantic_change_detect_annotation(Dict[str, int]) is False
assert is_pydantic_change_detect_annotation(Dict[str, OtherModel]) is False
assert is_pydantic_change_detect_annotation(dict[str, int]) is False
assert is_pydantic_change_detect_annotation(dict[str, OtherModel]) is False

assert is_pydantic_change_detect_annotation(Dict[str, SomeModel]) is True
assert is_pydantic_change_detect_annotation(dict[str, SomeModel]) is True
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[tox]
isolated_build = True
envlist =
py38-{1.9,1.10,2.6,2.x},
py39-{1.9,1.10,2.6,2.x},
py310-{1.9,1.10,2.6,2.x},
py311-{1.9,1.10,2.6,2.x},
py312-{1.9,1.10,2.6,2.x},
py313-{1.9,1.10,2.x}

[testenv]
deps =
Expand Down

0 comments on commit d0c4038

Please sign in to comment.