diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47537fd..7d033a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,15 +32,24 @@ jobs: - 27017:27017 steps: - uses: actions/checkout@v3 + - name: Install poetry + run: pipx install poetry - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + cache: 'poetry' + - name: Cache virtualenv + uses: actions/cache@v3 + with: + key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('poetry.lock') }} + path: .venv - name: Set up env run: | - python -m pip install -U -q poetry pip poetry install -q poetry run pip install -q "${{ matrix.django }}" - name: Run tests run: | + poetry run ruff . + poetry run black --check . poetry run python -m pytest diff --git a/Makefile b/Makefile index 648ca42..b285e0f 100644 --- a/Makefile +++ b/Makefile @@ -12,3 +12,8 @@ publish: test: poetry run python -m pytest + +codegen: + python codegen.py + black django_mongoengine/fields/__init__.py + ruff django_mongoengine/ --fix # It doesn't work with filename. diff --git a/codegen.py b/codegen.py new file mode 100644 index 0000000..b30221b --- /dev/null +++ b/codegen.py @@ -0,0 +1,31 @@ +def generate_fields(): + """ + Typing support cannot handle monkey-patching at runtime, so we need to generate fields explicitly. + """ + from mongoengine import fields + from django_mongoengine.fields import djangoflavor as mixins + + fields_code = str(_fields) + for fname in fields.__all__: + mixin_name = fname if hasattr(mixins, fname) else "DjangoField" + fields_code += f"class {fname}(_mixins.{mixin_name}, _fields.{fname}):\n pass\n" + + return fields_code + + +_fields = """ +from mongoengine import fields as _fields +from . import djangoflavor as _mixins +from django_mongoengine.utils.monkey import patch_mongoengine_field + +for f in ["StringField", "ObjectIdField"]: + patch_mongoengine_field(f) + +""" + +if __name__ == "__main__": + fname = "django_mongoengine/fields/__init__.py" + # This content required, because otherwise mixins import does not work. + open(fname, "w").write("from mongoengine.fields import *") + content = generate_fields() + open(fname, "w").write(content) diff --git a/django_mongoengine/document.py b/django_mongoengine/document.py index eca4056..fdf1d7b 100644 --- a/django_mongoengine/document.py +++ b/django_mongoengine/document.py @@ -2,7 +2,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from bson.objectid import ObjectId from django.db.models import Model @@ -11,11 +11,15 @@ from mongoengine import document as me from mongoengine.base import metaclasses as mtc from mongoengine.errors import FieldDoesNotExist +from typing_extensions import Self from .fields import ObjectIdField from .forms.document_options import DocumentMetaWrapper from .queryset import QuerySetManager +if TYPE_CHECKING: + from mongoengine.fields import StringField + # TopLevelDocumentMetaclass is using ObjectIdField to create default pk field, # if one's not set explicitly. # We need to know it's not editable and auto_created. @@ -43,11 +47,11 @@ def __new__(cls, name, bases, attrs): class DjangoFlavor: - id: Any - objects: Any = QuerySetManager() - _meta: DocumentMetaWrapper - _default_manager: Any = QuerySetManager() + id: StringField + objects = QuerySetManager[Self]() + _default_manager = QuerySetManager[Self]() _get_pk_val = Model.__dict__["_get_pk_val"] + _meta: DocumentMetaWrapper DoesNotExist: type[DoesNotExist] def __init__(self, *args, **kwargs): @@ -115,7 +119,7 @@ class DynamicDocument(DjangoFlavor, me.DynamicDocument): ... class EmbeddedDocument(DjangoFlavor, me.EmbeddedDocument): - ... + _instance: Document class DynamicEmbeddedDocument(DjangoFlavor, me.DynamicEmbeddedDocument): ... diff --git a/django_mongoengine/fields/__init__.py b/django_mongoengine/fields/__init__.py index a6b93f3..de68ad5 100644 --- a/django_mongoengine/fields/__init__.py +++ b/django_mongoengine/fields/__init__.py @@ -1,43 +1,182 @@ -from . import djangoflavor +from mongoengine import fields as _fields +from . import djangoflavor as _mixins +from django_mongoengine.utils.monkey import patch_mongoengine_field +for f in ["StringField", "ObjectIdField"]: + patch_mongoengine_field(f) -def init_module(): - """ - Create classes with Django-flavor mixins, - use DjangoField mixin as default - """ - import sys - from mongoengine import fields +class StringField(_mixins.StringField, _fields.StringField): + pass - current_module = sys.modules[__name__] - current_module.__all__ = fields.__all__ - for name in fields.__all__: - fieldcls = getattr(fields, name) - mixin = getattr(djangoflavor, name, djangoflavor.DjangoField) - setattr( - current_module, - name, - type(name, (mixin, fieldcls), {}), - ) +class URLField(_mixins.URLField, _fields.URLField): + pass -def patch_mongoengine_field(field_name): - """ - patch mongoengine.[field_name] for comparison support - becouse it's required in django.forms.models.fields_for_model - importing using mongoengine internal import cache - """ - from mongoengine import common +class EmailField(_mixins.EmailField, _fields.EmailField): + pass - field = common._import_class(field_name) - for k in ["__eq__", "__lt__", "__hash__", "attname", "get_internal_type"]: - if k not in field.__dict__: - setattr(field, k, djangoflavor.DjangoField.__dict__[k]) +class IntField(_mixins.IntField, _fields.IntField): + pass -init_module() -for f in ["StringField", "ObjectIdField"]: - patch_mongoengine_field(f) +class LongField(_mixins.DjangoField, _fields.LongField): + pass + + +class FloatField(_mixins.FloatField, _fields.FloatField): + pass + + +class DecimalField(_mixins.DecimalField, _fields.DecimalField): + pass + + +class BooleanField(_mixins.BooleanField, _fields.BooleanField): + pass + + +class DateTimeField(_mixins.DateTimeField, _fields.DateTimeField): + pass + + +class DateField(_mixins.DjangoField, _fields.DateField): + pass + + +class ComplexDateTimeField(_mixins.DjangoField, _fields.ComplexDateTimeField): + pass + + +class EmbeddedDocumentField(_mixins.EmbeddedDocumentField, _fields.EmbeddedDocumentField): + pass + + +class ObjectIdField(_mixins.DjangoField, _fields.ObjectIdField): + pass + + +class GenericEmbeddedDocumentField(_mixins.DjangoField, _fields.GenericEmbeddedDocumentField): + pass + + +class DynamicField(_mixins.DjangoField, _fields.DynamicField): + pass + + +class ListField(_mixins.ListField, _fields.ListField): + pass + + +class SortedListField(_mixins.DjangoField, _fields.SortedListField): + pass + + +class EmbeddedDocumentListField(_mixins.DjangoField, _fields.EmbeddedDocumentListField): + pass + + +class DictField(_mixins.DictField, _fields.DictField): + pass + + +class MapField(_mixins.DjangoField, _fields.MapField): + pass + + +class ReferenceField(_mixins.ReferenceField, _fields.ReferenceField): + pass + + +class CachedReferenceField(_mixins.DjangoField, _fields.CachedReferenceField): + pass + + +class LazyReferenceField(_mixins.DjangoField, _fields.LazyReferenceField): + pass + + +class GenericLazyReferenceField(_mixins.DjangoField, _fields.GenericLazyReferenceField): + pass + + +class GenericReferenceField(_mixins.DjangoField, _fields.GenericReferenceField): + pass + + +class BinaryField(_mixins.DjangoField, _fields.BinaryField): + pass + + +class GridFSError(_mixins.DjangoField, _fields.GridFSError): + pass + + +class GridFSProxy(_mixins.DjangoField, _fields.GridFSProxy): + pass + + +class FileField(_mixins.FileField, _fields.FileField): + pass + + +class ImageGridFsProxy(_mixins.DjangoField, _fields.ImageGridFsProxy): + pass + + +class ImproperlyConfigured(_mixins.ImproperlyConfigured, _fields.ImproperlyConfigured): + pass + + +class ImageField(_mixins.ImageField, _fields.ImageField): + pass + + +class GeoPointField(_mixins.DjangoField, _fields.GeoPointField): + pass + + +class PointField(_mixins.DjangoField, _fields.PointField): + pass + + +class LineStringField(_mixins.DjangoField, _fields.LineStringField): + pass + + +class PolygonField(_mixins.DjangoField, _fields.PolygonField): + pass + + +class SequenceField(_mixins.DjangoField, _fields.SequenceField): + pass + + +class UUIDField(_mixins.DjangoField, _fields.UUIDField): + pass + + +class EnumField(_mixins.DjangoField, _fields.EnumField): + pass + + +class MultiPointField(_mixins.DjangoField, _fields.MultiPointField): + pass + + +class MultiLineStringField(_mixins.DjangoField, _fields.MultiLineStringField): + pass + + +class MultiPolygonField(_mixins.DjangoField, _fields.MultiPolygonField): + pass + + +class GeoJsonBaseField(_mixins.DjangoField, _fields.GeoJsonBaseField): + pass + + +class Decimal128Field(_mixins.DjangoField, _fields.Decimal128Field): + pass diff --git a/django_mongoengine/forms/fields.py b/django_mongoengine/forms/fields.py index 4f75bce..8428233 100644 --- a/django_mongoengine/forms/fields.py +++ b/django_mongoengine/forms/fields.py @@ -107,7 +107,7 @@ def __init__(self, form, *args, **kwargs): kwargs['widget'] = EmbeddedFieldWidget(self.form.fields) kwargs['initial'] = [f.initial for f in self.form.fields.values()] kwargs['require_all_fields'] = False - super().__init__(fields=tuple([f for f in self.form.fields.values()]), *args, **kwargs) + super().__init__(fields=tuple(self.form.fields.values()), *args, **kwargs) def bound_data(self, data, initial): return data diff --git a/django_mongoengine/queryset.py b/django_mongoengine/queryset.py index b1e522b..62f622b 100644 --- a/django_mongoengine/queryset.py +++ b/django_mongoengine/queryset.py @@ -1,8 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Generic, TypeVar + from django.db.models.query import QuerySet as DjangoQuerySet from django.db.models.utils import resolve_callables from mongoengine import queryset as qs from mongoengine.errors import NotUniqueError +from .utils.monkey import patch_typing_support + +if TYPE_CHECKING: + from .document import Document + +_M = TypeVar("_M", bound="Document") + +patch_typing_support() + class QueryWrapper: # XXX: copy funcs from django; now it's just wrapper @@ -14,13 +27,13 @@ def __init__(self, q, ordering): self.order_by = ordering or [] -class BaseQuerySet: +class BaseQuerySet(Generic[_M]): """ A base queryset with django-required attributes """ @property - def model(self): + def model(self) -> type[_M]: return self._document @property @@ -46,7 +59,7 @@ def latest(self, field_name): def earliest(self, field_name): return self.order_by(field_name).first() - def exists(self): + def exists(self) -> bool: return bool(self) def _clone(self): @@ -106,13 +119,17 @@ def update_or_create(self, defaults=None, **kwargs): _extract_model_params = DjangoQuerySet.__dict__["_extract_model_params"] -class QuerySet(BaseQuerySet, qs.QuerySet): +class QuerySet(BaseQuerySet[_M], qs.QuerySet[_M]): pass -class QuerySetNoCache(BaseQuerySet, qs.QuerySetNoCache): +class QuerySetNoCache(BaseQuerySet[_M], qs.QuerySetNoCache[_M]): pass -class QuerySetManager(qs.QuerySetManager): +class QuerySetManager(Generic[_M], qs.QuerySetManager): default = QuerySet + if TYPE_CHECKING: + + def __get__(self, instance: object, cls: type[_M]) -> QuerySet[_M]: + ... diff --git a/django_mongoengine/utils/monkey.py b/django_mongoengine/utils/monkey.py index 9c51b9c..b7063ed 100644 --- a/django_mongoengine/utils/monkey.py +++ b/django_mongoengine/utils/monkey.py @@ -1,5 +1,10 @@ import importlib import importlib.util +from types import MethodType + +from mongoengine.queryset import QuerySet, QuerySetNoCache + +from django_mongoengine.fields.djangoflavor import DjangoField def get_patched_django_module(modname: str, **kwargs): @@ -16,3 +21,26 @@ def get_patched_django_module(modname: str, **kwargs): for k, v in kwargs.items(): setattr(module, k, v) return module + + +def patch_mongoengine_field(field_name: str): + """ + patch mongoengine.[field_name] for comparison support + becouse it's required in django.forms.models.fields_for_model + importing using mongoengine internal import cache + """ + from mongoengine import common + + field = common._import_class(field_name) + for k in ["__eq__", "__lt__", "__hash__", "attname", "get_internal_type"]: + if k not in field.__dict__: + setattr(field, k, DjangoField.__dict__[k]) + + +def patch_typing_support(): + """ + Patch classes to support generic types + """ + + for model in [QuerySet, QuerySetNoCache]: + model.__class_getitem__ = MethodType(lambda cls, _: cls, QuerySet) # type: ignore diff --git a/poetry.lock b/poetry.lock index 3ecea44..986197c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -85,6 +85,48 @@ files = [ [package.extras] tzdata = ["tzdata"] +[[package]] +name = "black" +version = "23.10.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, + {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, + {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, + {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, + {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, + {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, + {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, + {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, + {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, + {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, + {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.7.22" @@ -110,6 +152,20 @@ files = [ [package.extras] unicode-backport = ["unicodedata2"] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -416,6 +472,17 @@ files = [ [package.dependencies] pymongo = ">=3.4,<5.0" +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" version = "23.0" @@ -427,6 +494,17 @@ files = [ {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + [[package]] name = "pdbpp" version = "0.10.3" @@ -447,6 +525,21 @@ wmctrl = "*" funcsigs = ["funcsigs"] testing = ["funcsigs", "pytest"] +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -719,6 +812,32 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.1.3" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"}, + {file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"}, + {file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"}, + {file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"}, + {file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"}, + {file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"}, +] + [[package]] name = "setuptools" version = "67.2.0" @@ -911,6 +1030,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + [[package]] name = "tzdata" version = "2023.3" @@ -951,4 +1081,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "4162eec510279930014f0768ba7b66deafde189e3d969e9ab26fbb19fe3c683f" +content-hash = "fcd14244fbb71c85a98d1694f20a6d6f67e89e0eb6ffe5f82905ce0dddf1d509" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml index e5179f0..69874cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,23 +8,31 @@ authors = ["Ross Lawley "] python = ">=3.8" django = ">=3.2,<5" mongoengine = ">=0.14" +typing-extensions = "^4.8.0" -[tool.poetry.dev-dependencies] -sphinx = "*" -pytest = "*" +[tool.poetry.group.dev.dependencies] flake8 = "*" pdbpp = "*" -pytest-django = "*" +pytest = "*" pytest-cov = "*" +pytest-django = "*" pytest-sugar = "*" +ruff = "^0.1.3" +sphinx = "*" +black = "^23.10.1" [tool.black] line-length = 100 skip-string-normalization = true -[tool.isort] -line_length = 100 -known_first_party = ["django_mongoengine"] -multi_line_output = 3 -skip_gitignore = true -include_trailing_comma = true +[tool.ruff] +line-length = 100 +select = [ + "E", + "F", + "FA", + "T20", + "TCH", + "C4", +] +ignore = ["E501"] diff --git a/tests/forms/tests.py b/tests/forms/tests.py index c5f032a..c828afa 100644 --- a/tests/forms/tests.py +++ b/tests/forms/tests.py @@ -1,7 +1,7 @@ from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.forms.models import modelform_factory - +import pytest from django_mongoengine.forms import widgets from django_mongoengine.forms.documents import documentform_factory from django_mongoengine.forms.fields import DictField @@ -159,6 +159,8 @@ def test_rendering(self): self.field.widget.render('widget_name', data_dicts[data]) self._check_structure(self.field.widget, output_structures[data], 'test_rendering:2') + # I don't understand why it fails; disable this check for now + @pytest.mark.skip(reason="Broken for now") def test_static(self): self._init_field(force=True) structure = { @@ -204,8 +206,6 @@ def test_static(self): }, ], } - print("I don't understand why it fails; disable this check for now") - return self._check_structure(self.field.widget, structure, 'test_static:1') def _init_field(self, depth=None, force=False):