From c479dfb87ee0972ba5ee20ed69a1490c95760096 Mon Sep 17 00:00:00 2001 From: Savva Surenkov Date: Mon, 25 Mar 2024 15:35:04 +0400 Subject: [PATCH] Setup `pre-commit` (#53) * Setup and run `pre-commit` on the project * Update `README.md` with setup instructions --- .github/workflows/python-test.yml | 15 ++++++++++++- .pre-commit-config.yaml | 21 +++++++++++++++++++ README.md | 8 +++++++ django_pydantic_field/__init__.py | 1 + .../_migration_serializers.py | 1 + django_pydantic_field/compat/__init__.py | 4 ++-- django_pydantic_field/compat/django.py | 1 + django_pydantic_field/fields.py | 2 +- django_pydantic_field/fields.pyi | 10 +++++++-- django_pydantic_field/forms.py | 2 +- django_pydantic_field/forms.pyi | 11 +++++----- django_pydantic_field/rest_framework.py | 2 +- django_pydantic_field/v1/base.py | 3 +-- django_pydantic_field/v1/fields.py | 8 +++---- django_pydantic_field/v1/rest_framework.py | 15 ++++++------- django_pydantic_field/v2/forms.py | 1 + .../v2/rest_framework/openapi.py | 3 ++- django_pydantic_field/v2/types.py | 1 + pyproject.toml | 1 + 19 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 24ee7a2..e47a414 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -30,11 +30,24 @@ jobs: python-version: '3.x' - name: Install dependencies run: | - python -m pip install --upgrade pip python -m pip install -e .[dev,test] - name: Lint package run: mypy . + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install -e .[dev] + - name: Run pre-commit + run: pre-commit run --show-diff-on-failure --color=always --all-files + test: runs-on: ubuntu-latest strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0be55cd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +default_language_version: + python: python3.12 + +repos: + - repo: meta + hooks: + - id: check-hooks-apply + - repo: https://github.com/PyCQA/isort + rev: 5.13.0 + hooks: + - id: isort + args: [ "--settings-path", "./pyproject.toml", "--filter-files" ] + files: "^django_pydantic_field/" + exclude: ^.*\b(\.pytest_cache|\.venv|venv|tests)\b.*$ + - repo: https://github.com/psf/black + rev: 24.3.0 + hooks: + - id: black + args: [ "--config", "./pyproject.toml" ] + files: "^django_pydantic_field/" + exclude: ^.*\b(\.pytest_cache|\.venv|venv|tests)\b.*$ diff --git a/README.md b/README.md index 5132d89..c05c9db 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,14 @@ class FooClassBasedView(views.APIView): return Response([request.data]) ``` +## Contributing +To get `django-pydantic-field` up and running in development mode: +1. Clone this repo; +1. Create a virtual environment: `python -m venv .venv`; +1. Activate `.venv`: `. .venv/bin/activate`; +1. Install the project and its dependencies: `pip install -e .[dev,test]`; +1. Setup `pre-commit`: `pre-commit install`. + ## Acknowledgement * [Churkin Oleg](https://gist.github.com/Bahus/98a9848b1f8e2dcd986bf9f05dbf9c65) for his Gist as a source of inspiration; diff --git a/django_pydantic_field/__init__.py b/django_pydantic_field/__init__.py index 29e6ff9..9315c1c 100644 --- a/django_pydantic_field/__init__.py +++ b/django_pydantic_field/__init__.py @@ -1,5 +1,6 @@ from .fields import SchemaField as SchemaField + def __getattr__(name): if name == "_migration_serializers": module = __import__("django_pydantic_field._migration_serializers", fromlist=["*"]) diff --git a/django_pydantic_field/_migration_serializers.py b/django_pydantic_field/_migration_serializers.py index 20a834f..6abbbd0 100644 --- a/django_pydantic_field/_migration_serializers.py +++ b/django_pydantic_field/_migration_serializers.py @@ -1,4 +1,5 @@ import warnings + from .compat.django import * DEPRECATION_MSG = ( diff --git a/django_pydantic_field/compat/__init__.py b/django_pydantic_field/compat/__init__.py index 4374b6b..3257f51 100644 --- a/django_pydantic_field/compat/__init__.py +++ b/django_pydantic_field/compat/__init__.py @@ -1,5 +1,5 @@ +from .django import GenericContainer as GenericContainer +from .django import MigrationWriter as MigrationWriter 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 diff --git a/django_pydantic_field/compat/django.py b/django_pydantic_field/compat/django.py index af08042..ec88e87 100644 --- a/django_pydantic_field/compat/django.py +++ b/django_pydantic_field/compat/django.py @@ -14,6 +14,7 @@ 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 ty diff --git a/django_pydantic_field/fields.py b/django_pydantic_field/fields.py index d76f309..cae8913 100644 --- a/django_pydantic_field/fields.py +++ b/django_pydantic_field/fields.py @@ -1,4 +1,4 @@ -from .compat.imports import compat_getattr, compat_dir +from .compat.imports import compat_dir, compat_getattr __getattr__ = compat_getattr(__name__) __dir__ = compat_dir(__name__) diff --git a/django_pydantic_field/fields.pyi b/django_pydantic_field/fields.pyi index 6efad99..a484932 100644 --- a/django_pydantic_field/fields.pyi +++ b/django_pydantic_field/fields.pyi @@ -95,7 +95,10 @@ def SchemaField( **kwargs: te.Unpack[_SchemaFieldKwargs], ) -> ST: ... @ty.overload -@te.deprecated("Passing `json.dump` kwargs to `SchemaField` is not supported by Pydantic 2 and will be removed in the future versions.") +@te.deprecated( + "Passing `json.dump` kwargs to `SchemaField` is not supported by " + "Pydantic 2 and will be removed in the future versions." +) def SchemaField( schema: ty.Type[ST] | None | ty.ForwardRef = ..., config: ConfigType = ..., @@ -105,7 +108,10 @@ def SchemaField( **kwargs: te.Unpack[_DeprecatedSchemaFieldKwargs], ) -> ST | None: ... @ty.overload -@te.deprecated("Passing `json.dump` kwargs to `SchemaField` is not supported by Pydantic 2 and will be removed in the future versions.") +@te.deprecated( + "Passing `json.dump` kwargs to `SchemaField` is not supported by " + "Pydantic 2 and will be removed in the future versions." +) def SchemaField( schema: ty.Type[ST] | ty.ForwardRef = ..., config: ConfigType = ..., diff --git a/django_pydantic_field/forms.py b/django_pydantic_field/forms.py index d76f309..cae8913 100644 --- a/django_pydantic_field/forms.py +++ b/django_pydantic_field/forms.py @@ -1,4 +1,4 @@ -from .compat.imports import compat_getattr, compat_dir +from .compat.imports import compat_dir, compat_getattr __getattr__ = compat_getattr(__name__) __dir__ = compat_dir(__name__) diff --git a/django_pydantic_field/forms.pyi b/django_pydantic_field/forms.pyi index 646f3a7..da6e37e 100644 --- a/django_pydantic_field/forms.pyi +++ b/django_pydantic_field/forms.pyi @@ -2,13 +2,13 @@ from __future__ import annotations import json import typing as ty -import typing_extensions as te +import typing_extensions as te from django.forms.fields import JSONField from django.forms.widgets import Widget from django.utils.functional import _StrOrPromise -from .fields import ST, ConfigType, _ExportKwargs +from .fields import _ExportKwargs, ConfigType, ST __all__ = ("SchemaField",) @@ -38,7 +38,6 @@ class _JSONFieldKwargs(_CharFieldKwargs, total=False): class _SchemaFieldKwargs(_ExportKwargs, _JSONFieldKwargs, total=False): allow_null: bool | None - class _DeprecatedSchemaFieldKwargs(_SchemaFieldKwargs, total=False): allow_nan: ty.Any indent: ty.Any @@ -46,7 +45,6 @@ class _DeprecatedSchemaFieldKwargs(_SchemaFieldKwargs, total=False): skipkeys: ty.Any sort_keys: ty.Any - class SchemaField(JSONField, ty.Generic[ST]): @ty.overload def __init__( @@ -57,7 +55,10 @@ class SchemaField(JSONField, ty.Generic[ST]): **kwargs: te.Unpack[_SchemaFieldKwargs], ) -> None: ... @ty.overload - @te.deprecated("Passing `json.dump` kwargs to `SchemaField` is not supported by Pydantic 2 and will be removed in the future versions.") + @te.deprecated( + "Passing `json.dump` kwargs to `SchemaField` is not supported by " + "Pydantic 2 and will be removed in the future versions." + ) def __init__( self, schema: ty.Type[ST] | ty.ForwardRef | str, diff --git a/django_pydantic_field/rest_framework.py b/django_pydantic_field/rest_framework.py index d76f309..cae8913 100644 --- a/django_pydantic_field/rest_framework.py +++ b/django_pydantic_field/rest_framework.py @@ -1,4 +1,4 @@ -from .compat.imports import compat_getattr, compat_dir +from .compat.imports import compat_dir, compat_getattr __getattr__ = compat_getattr(__name__) __dir__ = compat_dir(__name__) diff --git a/django_pydantic_field/v1/base.py b/django_pydantic_field/v1/base.py index e5d7919..414e369 100644 --- a/django_pydantic_field/v1/base.py +++ b/django_pydantic_field/v1/base.py @@ -95,14 +95,13 @@ def prepare_schema(schema: "ModelType", owner: t.Any = None) -> None: def extract_export_kwargs(ctx: dict, extractor=dict.get) -> t.Dict[str, t.Any]: - """ Extract ``BaseModel.json()`` kwargs from ctx for field deconstruction/reconstruction.""" + """Extract ``BaseModel.json()`` kwargs from ctx for field deconstruction/reconstruction.""" export_ctx = dict( exclude_defaults=extractor(ctx, "exclude_defaults", None), exclude_none=extractor(ctx, "exclude_none", None), exclude_unset=extractor(ctx, "exclude_unset", None), by_alias=extractor(ctx, "by_alias", None), - # extract json.dumps(...) kwargs, see: https://docs.pydantic.dev/1.10/usage/exporting_models/#modeljson skipkeys=extractor(ctx, "skipkeys", None), indent=extractor(ctx, "indent", None), diff --git a/django_pydantic_field/v1/fields.py b/django_pydantic_field/v1/fields.py index 2a92bf4..df6ec70 100644 --- a/django_pydantic_field/v1/fields.py +++ b/django_pydantic_field/v1/fields.py @@ -1,6 +1,6 @@ +import contextlib import json import typing as t -import contextlib from functools import partial import pydantic @@ -188,8 +188,7 @@ def SchemaField( *args, null: "t.Literal[True]", **kwargs, -) -> "t.Optional[base.ST]": - ... +) -> "t.Optional[base.ST]": ... @t.overload @@ -200,8 +199,7 @@ def SchemaField( *args, null: "t.Literal[False]" = ..., **kwargs, -) -> "base.ST": - ... +) -> "base.ST": ... def SchemaField(schema=None, config=None, default=None, *args, **kwargs) -> t.Any: diff --git a/django_pydantic_field/v1/rest_framework.py b/django_pydantic_field/v1/rest_framework.py index 465bfe4..dc2721e 100644 --- a/django_pydantic_field/v1/rest_framework.py +++ b/django_pydantic_field/v1/rest_framework.py @@ -2,14 +2,14 @@ from django.conf import settings from pydantic import BaseModel, ValidationError - from rest_framework import exceptions, parsers, renderers, serializers from rest_framework.schemas import openapi from rest_framework.schemas.utils import is_list_view -from . import base from django_pydantic_field.compat.typing import get_args +from . import base + __all__ = ( "SchemaField", "SchemaRenderer", @@ -32,10 +32,7 @@ def get_schema(self, ctx: "RequestResponseContext") -> t.Optional[t.Type[BaseMod schema = self.get_annotation_schema(ctx) if self.require_explicit_schema and schema is None: - raise ValueError( - "Schema should be either explicitly set with annotation " - "or passed in the context" - ) + raise ValueError("Schema should be either explicitly set with annotation or passed in the context") return schema @@ -189,7 +186,7 @@ def map_renderers(self, path: str, method: str): return response_types def get_request_body(self, path: str, method: str): - if method not in ('PUT', 'PATCH', 'POST'): + if method not in ("PUT", "PATCH", "POST"): return {} self.request_media_types = self.map_parsers(path, method) @@ -201,10 +198,10 @@ def get_request_body(self, path: str, method: str): media_type, request_schema = request_type content_schemas[media_type] = {"schema": request_schema} else: - serializer_ref = self._get_reference(serializer) + serializer_ref = self._get_reference(serializer) content_schemas[request_type] = {"schema": serializer_ref} - return {'content': content_schemas} + return {"content": content_schemas} def get_responses(self, path: str, method: str): if method == "DELETE": diff --git a/django_pydantic_field/v2/forms.py b/django_pydantic_field/v2/forms.py index 357a468..ec0114c 100644 --- a/django_pydantic_field/v2/forms.py +++ b/django_pydantic_field/v2/forms.py @@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _ from django_pydantic_field.compat import deprecation + from . import types __all__ = ("SchemaField",) diff --git a/django_pydantic_field/v2/rest_framework/openapi.py b/django_pydantic_field/v2/rest_framework/openapi.py index 6faa5fa..458dcf7 100644 --- a/django_pydantic_field/v2/rest_framework/openapi.py +++ b/django_pydantic_field/v2/rest_framework/openapi.py @@ -8,9 +8,10 @@ from rest_framework.schemas import utils as drf_schema_utils from rest_framework.test import APIRequestFactory -from . import fields, parsers, renderers from django_pydantic_field.v2 import utils +from . import fields, parsers, renderers + if ty.TYPE_CHECKING: from collections.abc import Iterable diff --git a/django_pydantic_field/v2/types.py b/django_pydantic_field/v2/types.py index 7cfa372..72dcb2b 100644 --- a/django_pydantic_field/v2/types.py +++ b/django_pydantic_field/v2/types.py @@ -8,6 +8,7 @@ from django_pydantic_field.compat.django import GenericContainer from django_pydantic_field.compat.functools import cached_property + from . import utils if ty.TYPE_CHECKING: diff --git a/pyproject.toml b/pyproject.toml index 8e08c6a..400d91e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ dev = [ "black", "isort", "mypy", + "pre-commit", "pytest~=7.4", "djangorestframework>=3.11,<4", "django-stubs[compatible-mypy]~=4.2",