diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b02044..7c07666 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,12 +25,6 @@ jobs: container: - image: python:latest toxenv: lint - - image: python:3.6 - toxenv: py36-sqla12 - - image: python:3.6 - toxenv: py36-sqla13 - - image: python:3.6 - toxenv: py36-sqla14 - image: python:3.7 toxenv: py37-sqla12 - image: python:3.7 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a574c2a..ebc3de9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,19 @@ --- repos: -- repo: https://github.com/miki725/importanize - rev: '0.7' - hooks: - - id: importanize - language_version: python3 - - repo: https://github.com/psf/black rev: 22.6.0 hooks: - id: black language_version: python3 + +- repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + name: isort (python) + - repo: https://github.com/asottile/pyupgrade rev: v2.37.3 hooks: @@ -20,15 +21,14 @@ repos: args: [--py3-plus] - repo: https://github.com/myint/docformatter - rev: v1.4 + rev: v1.5.0-rc1 hooks: - id: docformatter -- repo: https://github.com/PyCQA/flake8 - rev: 5.0.2 +- repo: https://github.com/flakeheaven/flakeheaven + rev: 3.0.0 hooks: - - id: flake8 - exclude: deployment/roles + - id: flakeheaven additional_dependencies: - flake8-bugbear - flake8-comprehensions diff --git a/Dockerfile b/Dockerfile index 83db2c4..753cb92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,13 +18,11 @@ USER sorcerer ENV PATH=/opt/pyenv/bin:/home/sorcerer/.pyenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin WORKDIR /code -RUN pyenv install 3.6:latest RUN pyenv install 3.7:latest RUN pyenv install 3.8:latest RUN pyenv install 3.9:latest RUN pyenv install 3.10:latest -RUN pyenv install pypy3.6:latest RUN pyenv install pypy3.7:latest RUN pyenv install pypy3.8:latest diff --git a/MANIFEST.in b/MANIFEST.in index e955d47..80072c0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include *.txt include *.yaml *.yml include LICENSE include Makefile -include pytest.ini include tox.ini include Dockerfile exclude .* diff --git a/Makefile b/Makefile index 8a4c36d..f0f0824 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ next: # print next version @echo $(NEXT) bump: history - @sed -i 's/$(VERSION)/$(NEXT)/g' $(PACKAGE)/__version__.py + @sed -i 's/$(VERSION)/$(NEXT)/g' $(PACKAGE)/__init__.py @sed -i 's/Next version (unreleased yet)/$(NEXT) ($(shell date +"%Y-%m-%d"))/g' HISTORY.rst @git add . @git commit -am "Bump version: $(VERSION) → $(NEXT)" diff --git a/django_sorcery/__init__.py b/django_sorcery/__init__.py index 52bf790..4755e5d 100644 --- a/django_sorcery/__init__.py +++ b/django_sorcery/__init__.py @@ -1,14 +1,3 @@ -from .__version__ import ( - __author__, - __author_email__, - __description__, - __version__, -) +__version__ = "0.12.0" - -__all__ = [ - "__author__", - "__author_email__", - "__description__", - "__version__", -] +VERSION = tuple(__version__.split(".")) diff --git a/django_sorcery/__version__.py b/django_sorcery/__version__.py deleted file mode 100644 index 19443f5..0000000 --- a/django_sorcery/__version__.py +++ /dev/null @@ -1,4 +0,0 @@ -__author__ = "Serkan Hosca" -__author_email__ = "serkan@hosca.com" -__version__ = "0.12.0" -__description__ = "Django and SQLAlchemy integration" diff --git a/django_sorcery/db/__init__.py b/django_sorcery/db/__init__.py index e29e1ac..bd43ca5 100644 --- a/django_sorcery/db/__init__.py +++ b/django_sorcery/db/__init__.py @@ -141,5 +141,4 @@ from .sqlalchemy import SQLAlchemy # noqa from .utils import dbdict - databases = dbdict() diff --git a/django_sorcery/db/fields.py b/django_sorcery/db/fields.py index 2845c32..0e6ae3d 100644 --- a/django_sorcery/db/fields.py +++ b/django_sorcery/db/fields.py @@ -10,7 +10,6 @@ from .url import DIALECT_MAP_TO_DJANGO - __all__ = [ "BigIntegerField", "BinaryField", @@ -46,11 +45,8 @@ class Field(sa.Column): def __init__(self, *args, **kwargs): self.db = kwargs.pop("db", None) - name = None args = list(args) - if args and isinstance(args[0], str): - name = args.pop(0) - + name = args.pop(0) if args and isinstance(args[0], str) else None column_type = kwargs.pop("type_", None) if args and hasattr(args[0], "_sqla_type"): column_type = args.pop(0) @@ -267,7 +263,7 @@ def get_django_dialect_ranges(self): ops = operations.BaseDatabaseOperations with suppress(ImportError): ops = ( - import_string(DIALECT_MAP_TO_DJANGO.get(self.db.url.get_dialect().name) + ".base.DatabaseOperations") + import_string(f"{DIALECT_MAP_TO_DJANGO.get(self.db.url.get_dialect().name)}.base.DatabaseOperations") if self.db else operations.BaseDatabaseOperations ) diff --git a/django_sorcery/db/meta/column.py b/django_sorcery/db/meta/column.py index 6147493..09a4b83 100644 --- a/django_sorcery/db/meta/column.py +++ b/django_sorcery/db/meta/column.py @@ -4,8 +4,8 @@ import enum from contextlib import suppress -import sqlalchemy as sa from dateutil.parser import parse +import sqlalchemy as sa from django import forms as djangoforms from django.conf import settings from django.core import validators as djangovalidators @@ -166,7 +166,7 @@ def clean(self, value, instance): def validate(self, value, instance): """Validate value and raise ValidationError if necessary.""" - getattr(instance, "clean_" + self.name, bool)() + getattr(instance, f"clean_{self.name}", bool)() def run_validators(self, value): """Run field's validators and raise ValidationError if necessary.""" @@ -194,9 +194,7 @@ def __init__(self, column, prop=None, parent=None, name=None): self.field_kwargs["max_length"] = self.column.type.length def to_python(self, value): - if value is None: - return value - return str(value).strip() + return value if value is None else str(value).strip() class text_column_info(string_column_info): @@ -324,9 +322,7 @@ def to_python(self, value): return bool(value) if value in ("t", "T"): return True - if value in ("f", "F"): - return False - return self.coercer.to_python(value) + return False if value in ("f", "F") else self.coercer.to_python(value) class date_column_info(column_info): diff --git a/django_sorcery/db/meta/model.py b/django_sorcery/db/meta/model.py index cbcc1a0..cc2415f 100644 --- a/django_sorcery/db/meta/model.py +++ b/django_sorcery/db/meta/model.py @@ -1,12 +1,14 @@ """Metadata for sqlalchemy models.""" -from collections import OrderedDict, namedtuple +from collections import OrderedDict +from collections import namedtuple from functools import partial from itertools import chain import inflect import sqlalchemy as sa from django.apps import apps -from django.core.exceptions import FieldDoesNotExist, ValidationError +from django.core.exceptions import FieldDoesNotExist +from django.core.exceptions import ValidationError from ...exceptions import NestedValidationError from ...validators import ValidationRunner @@ -15,7 +17,6 @@ from .composite import composite_info from .relations import relation_info - Identity = namedtuple("Key", ["model", "pk"]) inflector = inflect.engine() @@ -75,8 +76,8 @@ def __init__(self, model): app_config = apps.get_containing_app_config(self.model_class.__module__) self.app_label = getattr(app_config, "label", None) or "django_sorcery" - self.label = "{}.{}".format(self.app_label, self.object_name) - self.label_lower = "{}.{}".format(self.app_label, self.model_name) + self.label = f"{self.app_label}.{self.object_name}" + self.label_lower = f"{self.app_label}.{self.model_name}" sa.event.listen(self.mapper, "mapper_configured", self._init) self._init(self.mapper, self.model_class) @@ -147,10 +148,11 @@ def __getattr__(self, name): def __repr__(self): reprs = ["".format(self.model_class.__name__)] - reprs.extend(" " + repr(i) for i in self.primary_keys.values()) - reprs.extend(" " + repr(i) for _, i in sorted(self.properties.items())) - reprs.extend(" " + i for i in chain(*[repr(c).split("\n") for _, c in sorted(self.composites.items())])) - reprs.extend(" " + repr(i) for _, i in sorted(self.relationships.items())) + reprs.extend(f" {repr(i)}" for i in self.primary_keys.values()) + reprs.extend(f" {repr(i)}" for _, i in sorted(self.properties.items())) + reprs.extend(f" {i}" for i in chain(*[repr(c).split("\n") for _, c in sorted(self.composites.items())])) + + reprs.extend(f" {repr(i)}" for _, i in sorted(self.relationships.items())) return "\n".join(reprs) @property @@ -185,10 +187,7 @@ def primary_keys_from_dict(self, kwargs): if any(pk is None for pk in pks): return None - if len(pks) < 2: - return next(iter(pks), None) - - return tuple(pks) + return next(iter(pks), None) if len(pks) < 2 else tuple(pks) def primary_keys_from_instance(self, instance): """Return a dict containing the primary keys of the ``instance``""" diff --git a/django_sorcery/db/meta/relations.py b/django_sorcery/db/meta/relations.py index dae1b94..d41fa3f 100644 --- a/django_sorcery/db/meta/relations.py +++ b/django_sorcery/db/meta/relations.py @@ -55,8 +55,8 @@ def __init__(self, relationship): self.local_remote_pairs_for_identity_key = [] try: # ensure local_remote pairs are of same order as remote pk - for i in target_pk: - self.local_remote_pairs_for_identity_key.append((pairs[i], i)) + self.local_remote_pairs_for_identity_key.extend((pairs[i], i) for i in target_pk) + except KeyError: # if relation is missing one of related pk columns # but actual constraint has it defined @@ -78,8 +78,8 @@ def __init__(self, relationship): if len(matching_constraints) == 1: pairs = {i.column: i.parent for i in matching_constraints[0].elements} - for i in target_pk: - self.local_remote_pairs_for_identity_key.append((pairs[i], i)) + self.local_remote_pairs_for_identity_key.extend((pairs[i], i) for i in target_pk) + else: # if everything fails, return default pairs self.local_remote_pairs_for_identity_key = self.local_remote_pairs[:] @@ -95,12 +95,10 @@ def formfield(self, form_class=None, **kwargs): return form_class(self.related_model, **field_kwargs) def get_form_class(self): - from ...fields import ModelChoiceField, ModelMultipleChoiceField - - if self.uselist: - return ModelMultipleChoiceField + from ...fields import ModelChoiceField + from ...fields import ModelMultipleChoiceField - return ModelChoiceField + return ModelMultipleChoiceField if self.uselist else ModelChoiceField def __repr__(self): - return "".format(self.parent_model.__name__, self.name) + return f"" diff --git a/django_sorcery/db/middleware.py b/django_sorcery/db/middleware.py index 0a5d3bf..5828657 100644 --- a/django_sorcery/db/middleware.py +++ b/django_sorcery/db/middleware.py @@ -4,7 +4,6 @@ from . import databases from .signals import all_signals - before_middleware_request = all_signals.signal("before_middleware_request") after_middleware_response = all_signals.signal("after_middleware_response") diff --git a/django_sorcery/db/models.py b/django_sorcery/db/models.py index 0f72e57..d0c97de 100644 --- a/django_sorcery/db/models.py +++ b/django_sorcery/db/models.py @@ -4,11 +4,13 @@ import sqlalchemy as sa import sqlalchemy.ext.declarative # noqa import sqlalchemy.orm # noqa +from sqlalchemy.orm.base import MANYTOONE +from sqlalchemy.orm.base import NO_VALUE from django.core.exceptions import ValidationError from django.utils.text import camel_case_to_spaces -from sqlalchemy.orm.base import MANYTOONE, NO_VALUE -from . import meta, signals +from . import meta +from . import signals from .mixins import CleanMixin @@ -231,9 +233,9 @@ class BaseMeta(sqlalchemy.ext.declarative.DeclarativeMeta): """Base metaclass for models which registers models to DB model registry when models are created.""" - def __new__(mcs, name, bases, attrs): - klass = super().__new__(mcs, name, bases, attrs) - mcs.db.models_registry.append(klass) + def __new__(cls, name, bases, attrs): + klass = super().__new__(cls, name, bases, attrs) + cls.db.models_registry.append(klass) return klass diff --git a/django_sorcery/db/profiler.py b/django_sorcery/db/profiler.py index 44c1feb..1d595a9 100644 --- a/django_sorcery/db/profiler.py +++ b/django_sorcery/db/profiler.py @@ -1,14 +1,14 @@ """sqlalchemy profiling things.""" import logging import time -from collections import defaultdict, namedtuple +from collections import defaultdict +from collections import namedtuple from functools import partial from threading import local import sqlalchemy as sa from django.conf import settings - logger = logging.getLogger(__name__) STATEMENT_TYPES = {"SELECT": "select", "INSERT INTO": "insert", "UPDATE": "update", "DELETE": "delete"} @@ -190,17 +190,20 @@ def process_response(self, request, response): try: stats = self.profiler.stats if stats["duration"] or self.log_results: - self.log(**{"sa_{}".format(k): v for k, v in stats.items()}) + self.log(**{f"sa_{k}": v for k, v in stats.items()}) except Exception: # pragma: nocover # The show must go on... pass # pragma: nocover else: if self.header_results: for k, v in stats.items(): - response["X-SA-{}".format("".join(i.title() for i in k.split("_")))] = v + response[f'X-SA-{"".join(i.title() for i in k.split("_"))}'] = v self.profiler.clear() return response def log(self, **kwargs): """Log sqlalchemy stats for current request.""" - self.logger.info("SQLAlchemy profiler %s", " ".join("{}={}".format(k, v) for k, v in kwargs.items())) + self.logger.info( + "SQLAlchemy profiler %s", + " ".join(f"{k}={v}" for k, v in kwargs.items()), + ) diff --git a/django_sorcery/db/query.py b/django_sorcery/db/query.py index 53a51c9..f191941 100644 --- a/django_sorcery/db/query.py +++ b/django_sorcery/db/query.py @@ -1,4 +1,5 @@ """sqlalchemy query related things.""" + from collections import namedtuple from functools import partial @@ -9,40 +10,26 @@ from ..utils import lower from . import meta - Operation = namedtuple("Operation", ["name", "args", "kwargs"]) # todo add transforms support - e.g. column__date__gt LOOKUP_TO_EXPRESSION = { "contains": lambda column, value: column.contains(value), - # "date" - # "day" "endswith": lambda column, value: column.endswith(value), "exact": lambda column, value: column == value, "gt": lambda column, value: column > value, "gte": lambda column, value: column >= value, - # "hour" "icontains": lambda column, value: sa.func.lower(column).contains(lower(value)), "iendswith": lambda column, value: sa.func.lower(column).endswith(lower(value)), "iexact": lambda column, value: sa.func.lower(column) == lower(value), "iin": lambda column, value: sa.func.lower(column).in_(lower(i) for i in value), "in": lambda column, value: column.in_(value), - # "iregex" - "isnull": lambda column, value: column == None if value else column != None, # noqa + "isnull": lambda column, value: column is None if value else column is not None, "istartswith": lambda column, value: sa.func.lower(column).startswith(lower(value)), "lt": lambda column, value: column < value, "lte": lambda column, value: column <= value, - # "minute" - # "month" - # "quarter" "range": lambda column, value: column.between(*value), - # "regex" - # "second" "startswith": lambda column, value: column.startswith(value), - # "time" - # "week" - # "week_day" - # "year" } @@ -59,11 +46,7 @@ def get(self, *args, **kwargs): mapper = self._only_full_mapper_zero("get") pk = meta.model_info(mapper).primary_keys_from_dict(kwargs) - if pk is not None: - return super().get(pk) - - return None - + return super().get(pk) if pk is not None else None return super().get(*args, **kwargs) def order_by(self, *criterion): @@ -237,9 +220,7 @@ def __get__(self, instance, owner): if not model: raise AttributeError( - "Cannot access {} when not bound to a model. " - "You can explicitly instantiate descriptor with model class - `db.queryproperty(Model)`." - "".format(self.__class__.__name__) + f"Cannot access {self.__class__.__name__} when not bound to a model. You can explicitly instantiate descriptor with model class - `db.queryproperty(Model)`." ) try: diff --git a/django_sorcery/db/session.py b/django_sorcery/db/session.py index 7ad25cf..212cdbd 100644 --- a/django_sorcery/db/session.py +++ b/django_sorcery/db/session.py @@ -1,7 +1,8 @@ """sqlalchemy session related things.""" from itertools import chain -from sqlalchemy import event, orm +from sqlalchemy import event +from sqlalchemy import orm from ..utils import setdefaultattr from . import signals diff --git a/django_sorcery/db/sqlalchemy.py b/django_sorcery/db/sqlalchemy.py index 86fa179..e89fe4e 100644 --- a/django_sorcery/db/sqlalchemy.py +++ b/django_sorcery/db/sqlalchemy.py @@ -8,10 +8,14 @@ from sqlalchemy.ext.declarative import declarative_base from ..utils import make_args -from . import fields, signals -from .composites import BaseComposite, CompositeField -from .models import Base, BaseMeta -from .query import Query, QueryProperty +from . import fields +from . import signals +from .composites import BaseComposite +from .composites import CompositeField +from .models import Base +from .models import BaseMeta +from .query import Query +from .query import QueryProperty from .relations import RelationsMixin from .session import SignallingSession from .transaction import TransactionContext @@ -32,8 +36,8 @@ def get(self): class _sqla_meta(type): - def __new__(mcs, name, bases, attrs): - typ = super().__new__(mcs, name, bases, attrs) + def __new__(cls, name, bases, attrs): + typ = super().__new__(cls, name, bases, attrs) # figure out all props to be proxied dummy = sa.orm.Session() diff --git a/django_sorcery/db/url.py b/django_sorcery/db/url.py index a500420..e1a9784 100644 --- a/django_sorcery/db/url.py +++ b/django_sorcery/db/url.py @@ -7,7 +7,6 @@ from django.utils.encoding import force_str from django.utils.module_loading import import_string - DIALECT_MAP = { "django.db.backends.sqlite3": "sqlite", "django.db.backends.postgresql": "postgresql", diff --git a/django_sorcery/db/utils.py b/django_sorcery/db/utils.py index 8afb5d2..9bdfd21 100644 --- a/django_sorcery/db/utils.py +++ b/django_sorcery/db/utils.py @@ -6,7 +6,8 @@ from .sqlalchemy import SQLAlchemy from .transaction import TransactionContext -from .url import get_settings, make_url +from .url import get_settings +from .url import make_url class dbdict(dict): diff --git a/django_sorcery/forms.py b/django_sorcery/forms.py index e73f43b..4b903f1 100644 --- a/django_sorcery/forms.py +++ b/django_sorcery/forms.py @@ -3,20 +3,14 @@ from contextlib import suppress from itertools import chain -from django.core.exceptions import ( - NON_FIELD_ERRORS, - ImproperlyConfigured, - ValidationError, -) +from django.core.exceptions import NON_FIELD_ERRORS +from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ValidationError from django.forms import ALL_FIELDS -from django.forms.forms import ( - BaseForm as DjangoBaseForm, - DeclarativeFieldsMetaclass, -) -from django.forms.models import ( - BaseModelForm as DjangoBaseModelForm, - ModelFormOptions, -) +from django.forms.forms import BaseForm as DjangoBaseForm +from django.forms.forms import DeclarativeFieldsMetaclass +from django.forms.models import BaseModelForm as DjangoBaseModelForm +from django.forms.models import ModelFormOptions from django.forms.utils import ErrorList from .db import meta @@ -197,11 +191,14 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass): def __new__(mcs, name, bases, attrs): cls = super().__new__(mcs, name, bases, attrs) - base_formfield_callback = None - for base in bases: - if hasattr(base, "Meta") and hasattr(base.Meta, "formfield_callback"): - base_formfield_callback = base.Meta.formfield_callback - break + base_formfield_callback = next( + ( + base.Meta.formfield_callback + for base in bases + if hasattr(base, "Meta") and hasattr(base.Meta, "formfield_callback") + ), + None, + ) formfield_callback = attrs.pop("formfield_callback", base_formfield_callback) @@ -337,7 +334,7 @@ def save(self, flush=True, **kwargs): if self.errors: raise ValueError( - "The {} could not be saved because the data didn't validate.".format(self.instance.__class__.__name__) + f"The {self.instance.__class__.__name__} could not be saved because the data didn't validate." ) if self.instance not in opts.session: @@ -378,7 +375,7 @@ def save_instance(self, instance=None, cleaned_data=None): def update_attribute(self, instance, name, field, value): """Provides hooks for updating form instance's attribute for a field with value.""" - field_setter = getattr(self, "set_" + name, None) + field_setter = getattr(self, f"set_{name}", None) if field_setter: field_setter(instance, name, field, value) else: @@ -414,7 +411,7 @@ def modelform_factory(model, form=ModelForm, formfield_callback=None, **kwargs): if formfield_callback: meta_.formfield_callback = staticmethod(formfield_callback) - class_name = model.__name__ + "Form" + class_name = f"{model.__name__}Form" if getattr(meta_, "fields", None) is None and getattr(meta_, "exclude", None) is None: raise ImproperlyConfigured( diff --git a/django_sorcery/formsets/base.py b/django_sorcery/formsets/base.py index 007cff6..f348c3a 100644 --- a/django_sorcery/formsets/base.py +++ b/django_sorcery/formsets/base.py @@ -2,11 +2,13 @@ from django.core.exceptions import ImproperlyConfigured from django.forms import fields as djangofields -from django.forms.formsets import BaseFormSet, formset_factory +from django.forms.formsets import BaseFormSet +from django.forms.formsets import formset_factory from django.forms.widgets import HiddenInput from ..db import meta -from ..forms import ModelForm, modelform_factory +from ..forms import ModelForm +from ..forms import modelform_factory class BaseModelFormSet(BaseFormSet): @@ -28,10 +30,7 @@ def __init__(self, data=None, files=None, auto_id="id_%s", prefix=None, queryset def initial_form_count(self): """Return the number of forms that are required in this FormSet.""" - if not (self.data or self.files): - return len(self.get_queryset()) - - return super().initial_form_count() + return super().initial_form_count() if (self.data or self.files) else len(self.get_queryset()) def _existing_object(self, pk): info = meta.model_info(self.model) @@ -52,7 +51,7 @@ def _construct_form(self, i, **kwargs): info = meta.model_info(self.model) pks = {} for name, pk_info in info.primary_keys.items(): - pk_key = "{}-{}".format(self.add_prefix(i), name) + pk_key = f"{self.add_prefix(i)}-{name}" pk_val = self.data.get(pk_key) pks[name] = pk_info.column.type.python_type(pk_val) if pk_val else None @@ -192,5 +191,5 @@ def modelformset_factory( validate_min=validate_min, validate_max=validate_max, ) - class_name = model.__name__ + "FormSet" + class_name = f"{model.__name__}FormSet" return type(form)(str(class_name), (FormSet,), {"model": model, "session": session}) diff --git a/django_sorcery/formsets/inline.py b/django_sorcery/formsets/inline.py index 7c0ef46..b113ce4 100644 --- a/django_sorcery/formsets/inline.py +++ b/django_sorcery/formsets/inline.py @@ -2,7 +2,8 @@ from ..db import meta from ..forms import ModelForm -from .base import BaseModelFormSet, modelformset_factory +from .base import BaseModelFormSet +from .base import modelformset_factory class BaseInlineFormSet(BaseModelFormSet): @@ -22,10 +23,7 @@ def get_default_prefix(cls): return cls.fk.name def initial_form_count(self): - if self.save_as_new: - return 0 - - return super().initial_form_count() + return 0 if self.save_as_new else super().initial_form_count() def save(self, flush=False, **kwargs): instances = super().save(flush=flush) @@ -51,7 +49,7 @@ def _get_foreign_key(relation, parent_model, model, fk_name=None): if len(relations) == 1: return relations[0] - raise ValueError("Couldn't find a relation from '{}' to '{}'.".format(parent_model.__name__, model.__name__)) + raise ValueError(f"Couldn't find a relation from '{parent_model.__name__}' to '{model.__name__}'.") def inlineformset_factory( diff --git a/django_sorcery/management/alembic.py b/django_sorcery/management/alembic.py index d32ae91..3b637ac 100644 --- a/django_sorcery/management/alembic.py +++ b/django_sorcery/management/alembic.py @@ -1,16 +1,21 @@ """Alembic Django command things.""" import os -from collections import OrderedDict, namedtuple +from collections import OrderedDict +from collections import namedtuple import alembic import alembic.config -from django.core.management.base import BaseCommand, CommandError -from django.utils.functional import cached_property from sqlalchemy.orm import configure_mappers +from django.core.management.base import BaseCommand +from django.core.management.base import CommandError +from django.utils.functional import cached_property -from ..db import alembic as sorcery_alembic, databases, meta, signals -from ..db.alembic import include_object, process_revision_directives - +from ..db import alembic as sorcery_alembic +from ..db import databases +from ..db import meta +from ..db import signals +from ..db.alembic import include_object +from ..db.alembic import process_revision_directives SORCERY_ALEMBIC_CONFIG_FOLDER = os.path.abspath(os.path.dirname(sorcery_alembic.__file__)) @@ -57,7 +62,7 @@ def get_app_config(self, app, db): version_table = ( getattr(app, "version_table", None) or db.kwargs.get("version_table") - or "alembic_version_%s" % app.label.lower().replace(".", "_") + or f'alembic_version_{app.label.lower().replace(".", "_")}' ) max_length = db.engine.dialect.max_identifier_length diff --git a/django_sorcery/management/base.py b/django_sorcery/management/base.py index 63c6a35..2f8376c 100644 --- a/django_sorcery/management/base.py +++ b/django_sorcery/management/base.py @@ -3,7 +3,8 @@ import os import sqlalchemy as sa -from django.core.management.base import BaseCommand, CommandParser +from django.core.management.base import BaseCommand +from django.core.management.base import CommandParser class NamespacedCommand(BaseCommand): @@ -48,9 +49,10 @@ def create_parser(self, prog_name, subcommand): args = sa.util.get_cls_kwargs(CommandParser) kwargs = { "cmd": None, - "prog": "{} {}".format(os.path.basename(prog_name), subcommand), + "prog": f"{os.path.basename(prog_name)} {subcommand}", "description": self.help or None, } + parser = CommandParser(**{k: v for k, v in kwargs.items() if k in args}) for name, command_cls in self.commands.items(): diff --git a/django_sorcery/management/commands/sorcery_createall.py b/django_sorcery/management/commands/sorcery_createall.py index 079a30a..bf24de1 100644 --- a/django_sorcery/management/commands/sorcery_createall.py +++ b/django_sorcery/management/commands/sorcery_createall.py @@ -1,7 +1,7 @@ """CreateAll command.""" -from django.core.management.base import BaseCommand from sqlalchemy.orm import configure_mappers +from django.core.management.base import BaseCommand from ...db import databases diff --git a/django_sorcery/management/commands/sorcery_dropall.py b/django_sorcery/management/commands/sorcery_dropall.py index 9d84264..ab93ed6 100644 --- a/django_sorcery/management/commands/sorcery_dropall.py +++ b/django_sorcery/management/commands/sorcery_dropall.py @@ -1,7 +1,7 @@ """DropAll command.""" -from django.core.management.base import BaseCommand from sqlalchemy.orm import configure_mappers +from django.core.management.base import BaseCommand from ...db import databases diff --git a/django_sorcery/routers.py b/django_sorcery/routers.py index 13e122a..4891eb1 100644 --- a/django_sorcery/routers.py +++ b/django_sorcery/routers.py @@ -6,7 +6,6 @@ from .db import meta - try: from django.conf.urls import url as re_path except ImportError: # pragma: no cover diff --git a/django_sorcery/shortcuts.py b/django_sorcery/shortcuts.py index 65c1495..35bc3e7 100644 --- a/django_sorcery/shortcuts.py +++ b/django_sorcery/shortcuts.py @@ -1,13 +1,12 @@ """Some Django like shortcuts that support sqlalchemy models.""" from contextlib import suppress +from sqlalchemy.exc import InvalidRequestError from django.core.exceptions import ImproperlyConfigured from django.http import Http404 -from sqlalchemy.exc import InvalidRequestError def _get_query(klass): - query = getattr(klass, "query", None) or getattr(klass, "objects", None) if query: @@ -32,7 +31,7 @@ def get_object_or_404(klass, *args, **kwargs): instance = query.get(*args, **kwargs) if instance is None: - raise Http404("No %s matches the given query." % query) + raise Http404(f"No {query} matches the given query.") return instance @@ -53,6 +52,6 @@ def get_list_or_404(klass, *args, **kwargs): query = query.filter_by(**kwargs) if not query.count(): - raise Http404("No %s matches the given query." % query) + raise Http404(f"No {query} matches the given query.") return query diff --git a/django_sorcery/validators/__init__.py b/django_sorcery/validators/__init__.py index 7280f08..0c0dbdb 100644 --- a/django_sorcery/validators/__init__.py +++ b/django_sorcery/validators/__init__.py @@ -1,3 +1,2 @@ -if True: - from .runner import ValidationRunner # noqa - from .base import * # noqa +from .base import * # noqa +from .runner import ValidationRunner # noqa diff --git a/django_sorcery/validators/base.py b/django_sorcery/validators/base.py index 9e5a0d4..b3aa266 100644 --- a/django_sorcery/validators/base.py +++ b/django_sorcery/validators/base.py @@ -1,9 +1,9 @@ """Validators.""" import sqlalchemy as sa +from sqlalchemy import inspect from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ -from sqlalchemy import inspect class ValidateTogetherModelFields: @@ -72,8 +72,7 @@ def __call__(self, m): if state.persistent: # need to exlude the current model since it's already in db pks = info.mapper.primary_key_from_instance(m) - for name, pk in zip(info.primary_keys, pks): - clauses.append(getattr(m.__class__, name) != pk) + clauses.extend(getattr(m.__class__, name) != pk for name, pk in zip(info.primary_keys, pks)) query = self.session.query(m.__class__).filter(*clauses) exists = self.session.query(sa.literal(True)).filter(query.exists()).scalar() diff --git a/django_sorcery/validators/runner.py b/django_sorcery/validators/runner.py index a8e765c..529c279 100644 --- a/django_sorcery/validators/runner.py +++ b/django_sorcery/validators/runner.py @@ -1,6 +1,7 @@ """Validation runner.""" -from django.core.exceptions import NON_FIELD_ERRORS, ValidationError +from django.core.exceptions import NON_FIELD_ERRORS +from django.core.exceptions import ValidationError from ..exceptions import NestedValidationError diff --git a/django_sorcery/views/base.py b/django_sorcery/views/base.py index 87b1246..25714d0 100644 --- a/django_sorcery/views/base.py +++ b/django_sorcery/views/base.py @@ -1,14 +1,15 @@ """Base model view things with sqlalchemy.""" from contextlib import suppress +from sqlalchemy import literal +from sqlalchemy.exc import InvalidRequestError from django.apps import apps from django.core.exceptions import ImproperlyConfigured -from django.core.paginator import InvalidPage, Paginator +from django.core.paginator import InvalidPage +from django.core.paginator import Paginator from django.http import Http404 from django.utils.translation import gettext from django.views.generic.base import ContextMixin -from sqlalchemy import literal -from sqlalchemy.exc import InvalidRequestError from ..db import meta @@ -83,11 +84,7 @@ def get_model_template_name(self): """Returns the base template path.""" model = self.get_model() app_config = apps.get_containing_app_config(model.__module__) - return "{}/{}{}.html".format( - model.__name__.lower() if app_config is None else app_config.label, - model.__name__.lower(), - self.template_name_suffix, - ) + return f"{model.__name__.lower() if app_config is None else app_config.label}/{model.__name__.lower()}{self.template_name_suffix}.html" class BaseMultipleObjectMixin(SQLAlchemyMixin): diff --git a/django_sorcery/views/detail.py b/django_sorcery/views/detail.py index 5970d43..aefe7e9 100644 --- a/django_sorcery/views/detail.py +++ b/django_sorcery/views/detail.py @@ -1,7 +1,8 @@ """Django detail view things for sqlalchemy.""" from django.core.exceptions import ImproperlyConfigured -from django.views.generic.base import TemplateResponseMixin, View +from django.views.generic.base import TemplateResponseMixin +from django.views.generic.base import View from .base import BaseSingleObjectMixin diff --git a/django_sorcery/views/edit.py b/django_sorcery/views/edit.py index 2ec69c5..6435d68 100644 --- a/django_sorcery/views/edit.py +++ b/django_sorcery/views/edit.py @@ -2,15 +2,14 @@ from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponseRedirect -from django.views.generic.base import TemplateResponseMixin, View +from django.views.generic.base import TemplateResponseMixin +from django.views.generic.base import View from django.views.generic.edit import FormMixin from .. import forms -from .detail import ( - BaseDetailView, - SingleObjectMixin, - SingleObjectTemplateResponseMixin, -) +from .detail import BaseDetailView +from .detail import SingleObjectMixin +from .detail import SingleObjectTemplateResponseMixin class ModelFormMixin(FormMixin, SingleObjectMixin): @@ -62,10 +61,7 @@ def post(self, request, *args, **kwargs): """Handle POST requests: instantiate a form instance with the passed POST variables and then check if it's valid.""" form = self.get_form() - if form.is_valid(): - return self.form_valid(form) - - return self.form_invalid(form) + return self.form_valid(form) if form.is_valid() else self.form_invalid(form) put = post diff --git a/django_sorcery/views/list.py b/django_sorcery/views/list.py index ab765c9..8c293bf 100644 --- a/django_sorcery/views/list.py +++ b/django_sorcery/views/list.py @@ -1,7 +1,8 @@ """Django list view things for sqlalchemy.""" from django.core.exceptions import ImproperlyConfigured -from django.views.generic.base import TemplateResponseMixin, View +from django.views.generic.base import TemplateResponseMixin +from django.views.generic.base import View from .base import BaseMultipleObjectMixin @@ -17,7 +18,7 @@ def get_context_object_name(self, object_list): return self.context_object_name model = self.get_model() - return "%s_list" % model.__name__.lower() + return f"{model.__name__.lower()}_list" def get_context_data(self, object_list=None, **kwargs): """Get the context for this view.""" diff --git a/django_sorcery/viewsets/__init__.py b/django_sorcery/viewsets/__init__.py index 9de63f1..bedf4a4 100644 --- a/django_sorcery/viewsets/__init__.py +++ b/django_sorcery/viewsets/__init__.py @@ -1,13 +1,12 @@ -from .base import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet -from .mixins import ( - CreateModelMixin, - DeleteModelMixin, - ListModelMixin, - ModelFormMixin, - RetrieveModelMixin, - UpdateModelMixin, -) - +from .base import GenericViewSet +from .base import ModelViewSet +from .base import ReadOnlyModelViewSet +from .mixins import CreateModelMixin +from .mixins import DeleteModelMixin +from .mixins import ListModelMixin +from .mixins import ModelFormMixin +from .mixins import RetrieveModelMixin +from .mixins import UpdateModelMixin __all__ = [ "GenericViewSet", diff --git a/django_sorcery/viewsets/base.py b/django_sorcery/viewsets/base.py index 1195aa2..99bdd64 100644 --- a/django_sorcery/viewsets/base.py +++ b/django_sorcery/viewsets/base.py @@ -4,7 +4,8 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.decorators import classonlymethod -from django.views.generic.base import TemplateResponseMixin, View +from django.views.generic.base import TemplateResponseMixin +from django.views.generic.base import View from . import mixins @@ -14,7 +15,7 @@ class GenericViewSet(TemplateResponseMixin, View): def get_template_names(self): - self.template_name_suffix = "_" + self.action + self.template_name_suffix = f"_{self.action}" names = [] try: @@ -96,7 +97,7 @@ def view(request, *args, **kwargs): # resolved URL. view.cls = cls view.initkwargs = initkwargs - view.suffix = initkwargs.get("suffix", None) + view.suffix = initkwargs.get("suffix") view.actions = actions return view diff --git a/django_sorcery/viewsets/mixins.py b/django_sorcery/viewsets/mixins.py index d85e1a1..d052524 100644 --- a/django_sorcery/viewsets/mixins.py +++ b/django_sorcery/viewsets/mixins.py @@ -7,7 +7,8 @@ from ..db import meta from ..forms import modelform_factory -from ..views.base import BaseMultipleObjectMixin, BaseSingleObjectMixin +from ..views.base import BaseMultipleObjectMixin +from ..views.base import BaseSingleObjectMixin class ListModelMixin(BaseMultipleObjectMixin): @@ -31,7 +32,7 @@ def list(self, request, *args, **kwargs): def get_list_context_object_name(self, object_list): """Get the name to use for the object.""" model = self.get_model() - return "%s_list" % model.__name__.lower() + return f"{model.__name__.lower()}_list" def get_list_context_data(self, **kwargs): """Returns context data for list action.""" @@ -126,10 +127,7 @@ def get_success_url(self): def process_form(self, form): """Checks if form is valid and processes accordingly.""" - if form.is_valid(): - return self.form_valid(form) - - return self.form_invalid(form) + return self.form_valid(form) if form.is_valid() else self.form_invalid(form) def form_valid(self, form): """Processes a valid form.""" diff --git a/docs/conf.py b/docs/conf.py index c202121..ed01e37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,12 +20,11 @@ import os import sys +import sphinx_rtd_theme import django import django.test.utils -import sphinx_rtd_theme from django.conf import settings - sys.path.insert(0, os.path.abspath("..")) sys.path.insert(0, os.path.abspath(".")) diff --git a/pyproject.toml b/pyproject.toml index 90dec94..fa359be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,112 @@ +[build-system] +requires = ["setuptools>=61.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "django-sorcery" +authors = [{name = "Serkan Hosca", email = "serkan@hosca.com"}] +description = "Django and SQLAlchemy integration" +license = {text = "MIT"} +readme = "README.rst" +keywords = ["sqlalchemy django framework forms"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Web Environment", + "Framework :: Django :: 1.11", + "Framework :: Django :: 2.0", + "Framework :: Django :: 2.1", + "Framework :: Django :: 2.2", + "Framework :: Django :: 3.0", + "Framework :: Django :: 3.1", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", +] +requires-python = ">= 3.7" +dependencies = [ + "alembic", + "blinker", + "inflect", + "django", + "python-dateutil", + "sqlalchemy<2", +] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/shosca/django-sorcery" +Documentation = "https://django-sorcery.readthedocs.io" +Source = "https://github.com/shosca/django-sorcery" + +[project.entry-points] +pytest11 = {django-sorcery = "django_sorcery.pytest_plugin"} + +[tool.setuptools] +include-package-data = true +license-files = ["LICENSE"] + +[tool.setuptools.packages.find] +exclude = [ + "tests", + "tests.*", +] +namespaces = false + +[tool.setuptools.dynamic] +version = {attr = "django_sorcery.__version__"} + +[tool.distutils.bdist_wheel] +universal = 1 + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--tb native -r sfxX -p no:django-sorcery" + +[tool.flakeheaven] +format = "grouped" +max_line_length = 120 +show_source = true +exclude = [ + "*.rst", + ".eggs", + ".tox", + ".git", + "migrations*", + "docs/*", +] + +[tool.flakeheaven.plugins] +"flake8-*" = ["+*"] +mccabe = ["+*"] +pycodestyle = ["+*", "-E203", "-E501", "-W503"] +pyflakes = ["+*"] +pylint = ["+*"] + +[tool.isort] +profile = "black" +src_paths = ["django_sorcery", "tests"] +known_sqlalchemy = "sqlalchemy" +known_django = "django" +sections = ["FUTURE","STDLIB","THIRDPARTY","SQLALCHEMY","DJANGO","FIRSTPARTY","LOCALFOLDER"] +no_lines_before = ["SQLALCHEMY", "DJANGO"] +force_single_line = true + [tool.black] line-length = 120 target-version = ['py35', 'py36', 'py37'] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 6f9b7fd..0000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = -p no:django-sorcery diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 685808b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[bdist_wheel] -universal = 1 - -[tool:pytest] -addopts= - --tb native - -r sfxX - -[flake8] -show-source = true -enable-extensions = B,G -max-line-length = 120 -exclude = .eggs,.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules -select = C,E,F,W,B,B950 -ignore = E501,W503 - -[importanize] -allow_plugins=True -plugins= - unused_imports -length=120 -groups= - stdlib - sitepackages - remainder - packages:sqlalchemy - packages:django - packages:django_sorcery - packages:tests - local diff --git a/setup.py b/setup.py index a3aea73..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,65 +1,3 @@ -#!/usr/bin/env python -# fmt:off -import os +from setuptools import setup -from setuptools import find_packages, setup - - -here = os.path.abspath(os.path.dirname(__file__)) - - -about = {} -with open(os.path.join(here, "django_sorcery", "__version__.py")) as f: - exec(f.read(), about) - - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname), "rb").read().decode("utf-8") - - -setup( - author=about["__author__"], - author_email=about["__author_email__"], - description=about["__description__"], - include_package_data=True, - install_requires=["alembic", "blinker", "inflect", "django", "python-dateutil", "sqlalchemy<2"], - license="MIT", - long_description=read("README.rst"), - name="django-sorcery", - packages=find_packages(exclude=["tests", "tests.*"]), - entry_points={"pytest11": ["django-sorcery = django_sorcery.pytest_plugin"]}, - url="https://github.com/shosca/django-sorcery", - version=about["__version__"], - keywords="sqlalchemy django framework forms", - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Web Environment", - "Framework :: Django :: 1.11", - "Framework :: Django :: 2.0", - "Framework :: Django :: 2.1", - "Framework :: Django :: 2.2", - "Framework :: Django :: 3.0", - "Framework :: Django :: 3.1", - "Framework :: Django :: 3.2", - "Framework :: Django", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content", - "Topic :: Internet :: WWW/HTTP :: WSGI", - "Topic :: Software Development :: Libraries :: Application Frameworks", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - project_urls={ - "Documentation": "https://django-sorcery.readthedocs.io", - "Source": "https://github.com/shosca/django-sorcery", - }, -) +setup() diff --git a/test_site/manage.py b/test_site/manage.py index a5d064f..3a647e9 100755 --- a/test_site/manage.py +++ b/test_site/manage.py @@ -2,7 +2,6 @@ import os import sys - sys.path.insert(0, "..") if __name__ == "__main__": diff --git a/test_site/polls/admin.py b/test_site/polls/admin.py index 12a5f80..6af52da 100644 --- a/test_site/polls/admin.py +++ b/test_site/polls/admin.py @@ -1,4 +1,3 @@ from django.contrib import admin # noqa - # Register your models here. diff --git a/test_site/polls/apps.py b/test_site/polls/apps.py index 02c4930..9c01d31 100644 --- a/test_site/polls/apps.py +++ b/test_site/polls/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig from sqlalchemy.orm import configure_mappers +from django.apps import AppConfig class PollsConfig(AppConfig): diff --git a/test_site/polls/models.py b/test_site/polls/models.py index 65443ad..7f8e0a4 100644 --- a/test_site/polls/models.py +++ b/test_site/polls/models.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - db = databases.get("default") diff --git a/test_site/polls/tests.py b/test_site/polls/tests.py index 52f5853..9a30df3 100644 --- a/test_site/polls/tests.py +++ b/test_site/polls/tests.py @@ -1,4 +1,3 @@ from django.test import TestCase # noqa - # Create your tests here. diff --git a/test_site/polls/urls.py b/test_site/polls/urls.py index 7f1bc40..367dc31 100644 --- a/test_site/polls/urls.py +++ b/test_site/polls/urls.py @@ -1,9 +1,10 @@ -from django.urls import include, path +from django.urls import include +from django.urls import path + from django_sorcery.routers import SimpleRouter from . import views - router = SimpleRouter() router.register("", views.PollsViewSet) diff --git a/test_site/polls/views.py b/test_site/polls/views.py index 573bb56..34ff652 100644 --- a/test_site/polls/views.py +++ b/test_site/polls/views.py @@ -1,11 +1,14 @@ from django.http import HttpResponseRedirect -from django.urls import reverse, reverse_lazy +from django.urls import reverse +from django.urls import reverse_lazy + from django_sorcery.formsets import inlineformset_factory from django_sorcery.routers import action from django_sorcery.viewsets import ModelViewSet -from .models import Choice, Question, db - +from .models import Choice +from .models import Question +from .models import db ChoiceFormSet = inlineformset_factory(relation=Question.choices, fields=(Choice.choice_text.key,), session=db) @@ -25,9 +28,7 @@ def get_form_context_data(self, **kwargs): def get_choice_formset(self, instance=None): if not hasattr(self, "_choice_formset"): instance = instance or self.object - self._choice_formset = ChoiceFormSet( - instance=instance, data=self.request.POST if self.request.POST else None - ) + self._choice_formset = ChoiceFormSet(instance=instance, data=self.request.POST or None) return self._choice_formset diff --git a/test_site/test_site/settings.py b/test_site/test_site/settings.py index f62eb12..deaafba 100644 --- a/test_site/test_site/settings.py +++ b/test_site/test_site/settings.py @@ -11,7 +11,6 @@ import os - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/test_site/test_site/urls.py b/test_site/test_site/urls.py index 7f87036..a079934 100644 --- a/test_site/test_site/urls.py +++ b/test_site/test_site/urls.py @@ -15,7 +15,7 @@ """ from django.contrib import admin -from django.urls import include, path - +from django.urls import include +from django.urls import path urlpatterns = [path("admin/", admin.site.urls), path("polls/", include("polls.urls"))] diff --git a/test_site/test_site/wsgi.py b/test_site/test_site/wsgi.py index e98d12b..464636f 100644 --- a/test_site/test_site/wsgi.py +++ b/test_site/test_site/wsgi.py @@ -10,7 +10,6 @@ from django.core.wsgi import get_wsgi_application - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_site.settings") application = get_wsgi_application() diff --git a/tests/__init__.py b/tests/__init__.py index 3d9e322..a37c3a1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,8 +1,7 @@ import os -import django from psycopg2cffi import compat - +import django compat.register() diff --git a/tests/base.py b/tests/base.py index f8d1035..f961b8b 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,7 +1,7 @@ from bs4 import BeautifulSoup from django import test -from django_sorcery.db import databases +from django_sorcery.db import databases try: from unittest import mock # noqa diff --git a/tests/db/alembic/test_base.py b/tests/db/alembic/test_base.py index 6059799..95f4826 100644 --- a/tests/db/alembic/test_base.py +++ b/tests/db/alembic/test_base.py @@ -1,5 +1,6 @@ import alembic.config from django.test import TestCase + from django_sorcery.db.alembic.base import setup_config diff --git a/tests/db/alembic/test_signals.py b/tests/db/alembic/test_signals.py index feb3183..dcfa8e1 100644 --- a/tests/db/alembic/test_signals.py +++ b/tests/db/alembic/test_signals.py @@ -1,9 +1,8 @@ from django.test import TestCase + from django_sorcery.db import signals -from django_sorcery.db.alembic.signals import ( - include_object, - process_revision_directives, -) +from django_sorcery.db.alembic.signals import include_object +from django_sorcery.db.alembic.signals import process_revision_directives class TestIncludeObject(TestCase): diff --git a/tests/db/meta/test_column.py b/tests/db/meta/test_column.py index 7b04a82..f45f8d8 100644 --- a/tests/db/meta/test_column.py +++ b/tests/db/meta/test_column.py @@ -1,5 +1,8 @@ import enum -from datetime import date, datetime, time, timedelta +from datetime import date +from datetime import datetime +from datetime import time +from datetime import timedelta from decimal import Decimal import pytz @@ -7,18 +10,20 @@ from django import forms as djangoforms from django.core import validators as django_validators from django.core.exceptions import ValidationError -from django.forms import fields as djangofields, widgets +from django.forms import fields as djangofields +from django.forms import widgets + from django_sorcery import fields as sorceryfields -from django_sorcery.db import fields as dbfields, meta - -from ...base import TestCase, mock -from ...testapp.models import ( - COLORS, - AllKindsOfFields, - Business, - Vehicle, - VehicleType, -) +from django_sorcery.db import fields as dbfields +from django_sorcery.db import meta + +from ...base import TestCase +from ...base import mock +from ...testapp.models import COLORS +from ...testapp.models import AllKindsOfFields +from ...testapp.models import Business +from ...testapp.models import Vehicle +from ...testapp.models import VehicleType class TestColumnMeta(TestCase): diff --git a/tests/db/meta/test_composite.py b/tests/db/meta/test_composite.py index 050d41c..8be1d90 100644 --- a/tests/db/meta/test_composite.py +++ b/tests/db/meta/test_composite.py @@ -1,8 +1,12 @@ from django.core.exceptions import ValidationError + from django_sorcery.db import meta # noqa from ...base import TestCase -from ...testapp.models import Address, Business, Point, Vertex +from ...testapp.models import Address +from ...testapp.models import Business +from ...testapp.models import Point +from ...testapp.models import Vertex class TestCompositeMeta(TestCase): diff --git a/tests/db/meta/test_model.py b/tests/db/meta/test_model.py index e527274..00ee74b 100644 --- a/tests/db/meta/test_model.py +++ b/tests/db/meta/test_model.py @@ -1,11 +1,15 @@ import sqlalchemy as sa from django.apps import apps from django.core.exceptions import FieldDoesNotExist + from django_sorcery.db import meta # noqa from ...base import TestCase from ...otherapp.models import OtherAppInOtherApp -from ...testapp.models import Business, CompositePkModel, Owner, Vehicle +from ...testapp.models import Business +from ...testapp.models import CompositePkModel +from ...testapp.models import Owner +from ...testapp.models import Vehicle class TestModelMeta(TestCase): diff --git a/tests/db/meta/test_relations.py b/tests/db/meta/test_relations.py index 942edfa..de7cd1d 100644 --- a/tests/db/meta/test_relations.py +++ b/tests/db/meta/test_relations.py @@ -1,11 +1,14 @@ import sqlalchemy as sa from django.core.exceptions import ImproperlyConfigured + from django_sorcery import fields from django_sorcery.db import meta from ...base import TestCase from ...models_terrible_relations import Foo -from ...testapp.models import Owner, Part, Vehicle +from ...testapp.models import Owner +from ...testapp.models import Part +from ...testapp.models import Vehicle class TestRelationshipMeta(TestCase): diff --git a/tests/db/test_composites.py b/tests/db/test_composites.py index 4c5fa0e..b609824 100644 --- a/tests/db/test_composites.py +++ b/tests/db/test_composites.py @@ -1,7 +1,10 @@ from django_sorcery.db.composites import CompositeField from ..base import TestCase -from ..testapp.models import Address, Business, States, db +from ..testapp.models import Address +from ..testapp.models import Business +from ..testapp.models import States +from ..testapp.models import db class TestComposite(TestCase): diff --git a/tests/db/test_fields.py b/tests/db/test_fields.py index b45064c..acf30bb 100644 --- a/tests/db/test_fields.py +++ b/tests/db/test_fields.py @@ -4,10 +4,13 @@ from django import forms as djangoforms from django.core import validators as django_validators from django.forms import fields as djangofields + from django_sorcery import fields as formfields -from django_sorcery.db import fields, meta +from django_sorcery.db import fields +from django_sorcery.db import meta -from ..base import TestCase, mock +from ..base import TestCase +from ..base import mock class DummyEnum(enum.Enum): diff --git a/tests/db/test_middleware.py b/tests/db/test_middleware.py index 59da4bb..763d523 100644 --- a/tests/db/test_middleware.py +++ b/tests/db/test_middleware.py @@ -1,7 +1,10 @@ import unittest import attr -from django_sorcery.db import SQLAlchemy, databases, middleware + +from django_sorcery.db import SQLAlchemy +from django_sorcery.db import databases +from django_sorcery.db import middleware from ..base import mock diff --git a/tests/db/test_mixins.py b/tests/db/test_mixins.py index 83cf39b..08f28d0 100644 --- a/tests/db/test_mixins.py +++ b/tests/db/test_mixins.py @@ -1,15 +1,13 @@ from django.core.exceptions import ValidationError from ..base import TestCase -from ..testapp.models import ( - Address, - Business, - Owner, - Part, - Vehicle, - VehicleType, - db, -) +from ..testapp.models import Address +from ..testapp.models import Business +from ..testapp.models import Owner +from ..testapp.models import Part +from ..testapp.models import Vehicle +from ..testapp.models import VehicleType +from ..testapp.models import db class TestCleanMixin(TestCase): diff --git a/tests/db/test_models.py b/tests/db/test_models.py index 03e855b..87000a2 100644 --- a/tests/db/test_models.py +++ b/tests/db/test_models.py @@ -2,23 +2,23 @@ from decimal import Decimal from django.core.exceptions import ValidationError -from django_sorcery.db import meta, models + +from django_sorcery.db import meta +from django_sorcery.db import models from django_sorcery.utils import make_args from ..base import TestCase -from ..testapp.models import ( - Address, - AllKindsOfFields, - Business, - DummyEnum, - Option, - Owner, - Part, - SelectedAutoCoerce, - Vehicle, - VehicleType, - db, -) +from ..testapp.models import Address +from ..testapp.models import AllKindsOfFields +from ..testapp.models import Business +from ..testapp.models import DummyEnum +from ..testapp.models import Option +from ..testapp.models import Owner +from ..testapp.models import Part +from ..testapp.models import SelectedAutoCoerce +from ..testapp.models import Vehicle +from ..testapp.models import VehicleType +from ..testapp.models import db class TestModelRepr(TestCase): diff --git a/tests/db/test_profiler.py b/tests/db/test_profiler.py index d1a0652..8afd50f 100644 --- a/tests/db/test_profiler.py +++ b/tests/db/test_profiler.py @@ -1,12 +1,14 @@ from django.http import HttpResponse -from django.test import RequestFactory, override_settings -from django_sorcery.db.profiler import ( - SQLAlchemyProfiler, - SQLAlchemyProfilingMiddleware, -) +from django.test import RequestFactory +from django.test import override_settings + +from django_sorcery.db.profiler import SQLAlchemyProfiler +from django_sorcery.db.profiler import SQLAlchemyProfilingMiddleware from ..base import TestCase -from ..testapp.models import Business, Owner, db +from ..testapp.models import Business +from ..testapp.models import Owner +from ..testapp.models import db def get_response(request): diff --git a/tests/db/test_query.py b/tests/db/test_query.py index 5a21c13..9b32459 100644 --- a/tests/db/test_query.py +++ b/tests/db/test_query.py @@ -1,16 +1,15 @@ from django.conf import settings + from django_sorcery.db.query import QueryProperty from ..base import TestCase -from ..testapp.models import ( - CompositePkModel, - Owner, - Point, - Vehicle, - VehicleType, - Vertex, - db, -) +from ..testapp.models import CompositePkModel +from ..testapp.models import Owner +from ..testapp.models import Point +from ..testapp.models import Vehicle +from ..testapp.models import VehicleType +from ..testapp.models import Vertex +from ..testapp.models import db class TestQuery(TestCase): diff --git a/tests/db/test_session.py b/tests/db/test_session.py index 64f8e9b..ee70598 100644 --- a/tests/db/test_session.py +++ b/tests/db/test_session.py @@ -1,7 +1,10 @@ from django_sorcery.db import signals # noqa from ..base import TestCase -from ..testapp.models import CompositePkModel, Owner, OwnerQuery, db +from ..testapp.models import CompositePkModel +from ..testapp.models import Owner +from ..testapp.models import OwnerQuery +from ..testapp.models import db class TestSession(TestCase): diff --git a/tests/db/test_sqlalchemy.py b/tests/db/test_sqlalchemy.py index df3414a..d2475b0 100644 --- a/tests/db/test_sqlalchemy.py +++ b/tests/db/test_sqlalchemy.py @@ -2,10 +2,13 @@ import sqlalchemy as sa from django.conf import settings + from django_sorcery.db.query import Operation from ..base import TestCase -from ..testapp.models import ModelOne, Owner, db +from ..testapp.models import ModelOne +from ..testapp.models import Owner +from ..testapp.models import db class TestSQLAlchemy(TestCase): diff --git a/tests/db/test_transaction.py b/tests/db/test_transaction.py index 9ac0eeb..b1868c7 100644 --- a/tests/db/test_transaction.py +++ b/tests/db/test_transaction.py @@ -1,8 +1,10 @@ from django.core.exceptions import ValidationError + from django_sorcery.db.transaction import TransactionContext from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db class TestTransaction(TestCase): diff --git a/tests/db/test_url.py b/tests/db/test_url.py index 30209f5..0eca197 100644 --- a/tests/db/test_url.py +++ b/tests/db/test_url.py @@ -1,16 +1,16 @@ import os -from django.test import TestCase, override_settings -from django_sorcery.db.url import ( - boolean, - importable, - importable_list, - importable_list_tuples, - integer, - make_url, - string, - string_list, -) +from django.test import TestCase +from django.test import override_settings + +from django_sorcery.db.url import boolean +from django_sorcery.db.url import importable +from django_sorcery.db.url import importable_list +from django_sorcery.db.url import importable_list_tuples +from django_sorcery.db.url import integer +from django_sorcery.db.url import make_url +from django_sorcery.db.url import string +from django_sorcery.db.url import string_list @override_settings( diff --git a/tests/db/test_utils.py b/tests/db/test_utils.py index 7478021..180f74b 100644 --- a/tests/db/test_utils.py +++ b/tests/db/test_utils.py @@ -2,12 +2,17 @@ import sqlalchemy as sa from django.conf import settings + from django_sorcery.db import databases from django_sorcery.db.sqlalchemy import SQLAlchemy -from django_sorcery.db.utils import dbdict, index_foreign_keys +from django_sorcery.db.utils import dbdict +from django_sorcery.db.utils import index_foreign_keys from ..base import mock -from ..models_multidb import Bar, Foo, default_db, other_db +from ..models_multidb import Bar +from ..models_multidb import Foo +from ..models_multidb import default_db +from ..models_multidb import other_db class TestDbDict(unittest.TestCase): diff --git a/tests/formsets/test_base.py b/tests/formsets/test_base.py index 3208798..353b79b 100644 --- a/tests/formsets/test_base.py +++ b/tests/formsets/test_base.py @@ -1,9 +1,11 @@ from django.core.exceptions import ImproperlyConfigured + from django_sorcery.forms import ALL_FIELDS from django_sorcery.formsets import modelformset_factory from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db class TestModelFormSet(TestCase): @@ -51,9 +53,7 @@ def test_render(self): "

", ' ', ' ', - ' '.format( - self.owners[0].id - ), + f' ', "

", "

", ' ', @@ -62,9 +62,7 @@ def test_render(self): "

", ' ', ' ', - ' '.format( - self.owners[1].id - ), + f' ', "

", "

", ' ', @@ -73,9 +71,7 @@ def test_render(self): "

", ' ', ' ', - ' '.format( - self.owners[2].id - ), + f' ', "

", "

", ' ', @@ -84,9 +80,7 @@ def test_render(self): "

", ' ', ' ', - ' '.format( - self.owners[3].id - ), + f' ', "

", "

", ' ', @@ -99,6 +93,7 @@ def test_render(self): ] ) ) + self.assertHTMLEqual(soup.prettify(), expected_soup.prettify()) def test_edit(self): @@ -111,17 +106,17 @@ def test_edit(self): "form-MIN_NUM_FORMS": "0", "form-MAX_NUM_FORMS": "1000", "form-0-id": self.owners[0].id, - "form-0-first_name": "Edited first name {}".format(self.owners[0].id), - "form-0-last_name": "Edited last name {}".format(self.owners[0].id), + "form-0-first_name": f"Edited first name {self.owners[0].id}", + "form-0-last_name": f"Edited last name {self.owners[0].id}", "form-1-id": self.owners[1].id, - "form-1-first_name": "Edited first name {}".format(self.owners[1].id), - "form-1-last_name": "Edited last name {}".format(self.owners[1].id), + "form-1-first_name": f"Edited first name {self.owners[1].id}", + "form-1-last_name": f"Edited last name {self.owners[1].id}", "form-2-id": self.owners[2].id, - "form-2-first_name": "Edited first name {}".format(self.owners[2].id), - "form-2-last_name": "Edited last name {}".format(self.owners[2].id), + "form-2-first_name": f"Edited first name {self.owners[2].id}", + "form-2-last_name": f"Edited last name {self.owners[2].id}", "form-3-id": self.owners[3].id, - "form-3-first_name": "Edited first name {}".format(self.owners[3].id), - "form-3-last_name": "Edited last name {}".format(self.owners[3].id), + "form-3-first_name": f"Edited first name {self.owners[3].id}", + "form-3-last_name": f"Edited last name {self.owners[3].id}", "form-4-id": "", "form-4-first_name": "New first name", "form-4-last_name": "New last name", @@ -134,8 +129,8 @@ def test_edit(self): for owner in instances: if owner.first_name.startswith("Edited"): - self.assertEqual(owner.first_name, "Edited first name {}".format(owner.id)) - self.assertEqual(owner.last_name, "Edited last name {}".format(owner.id)) + self.assertEqual(owner.first_name, f"Edited first name {owner.id}") + self.assertEqual(owner.last_name, f"Edited last name {owner.id}") else: self.assertEqual(owner.first_name, "New first name") self.assertEqual(owner.last_name, "New last name") @@ -189,9 +184,7 @@ def test_delete_render(self): "

", ' ', ' ', - ' '.format( - self.owners[0].id - ), + f' ', "

", "

", ' ', @@ -204,9 +197,7 @@ def test_delete_render(self): "

", ' ', ' ', - ' '.format( - self.owners[1].id - ), + f' ', "

", "

", ' ', @@ -219,9 +210,7 @@ def test_delete_render(self): "

", ' ', ' ', - ' '.format( - self.owners[2].id - ), + f' ', "

", "

", ' ', @@ -234,9 +223,7 @@ def test_delete_render(self): "

", ' ', ' ', - ' '.format( - self.owners[3].id - ), + f' ', "

", "

", ' ', @@ -253,6 +240,7 @@ def test_delete_render(self): ] ) ) + self.assertHTMLEqual(soup.prettify(), expected_soup.prettify()) def test_delete(self): @@ -265,17 +253,17 @@ def test_delete(self): "form-MIN_NUM_FORMS": "0", "form-MAX_NUM_FORMS": "1000", "form-0-id": self.owners[0].id, - "form-0-first_name": "Edited first name {}".format(self.owners[0].id), - "form-0-last_name": "Edited last name {}".format(self.owners[0].id), + "form-0-first_name": f"Edited first name {self.owners[0].id}", + "form-0-last_name": f"Edited last name {self.owners[0].id}", "form-1-id": self.owners[1].id, - "form-1-first_name": "Edited first name {}".format(self.owners[1].id), - "form-1-last_name": "Edited last name {}".format(self.owners[1].id), + "form-1-first_name": f"Edited first name {self.owners[1].id}", + "form-1-last_name": f"Edited last name {self.owners[1].id}", "form-2-id": self.owners[2].id, - "form-2-first_name": "Edited first name {}".format(self.owners[2].id), - "form-2-last_name": "Edited last name {}".format(self.owners[2].id), + "form-2-first_name": f"Edited first name {self.owners[2].id}", + "form-2-last_name": f"Edited last name {self.owners[2].id}", "form-3-id": self.owners[3].id, - "form-3-first_name": "Edited first name {}".format(self.owners[3].id), - "form-3-last_name": "Edited last name {}".format(self.owners[3].id), + "form-3-first_name": f"Edited first name {self.owners[3].id}", + "form-3-last_name": f"Edited last name {self.owners[3].id}", "form-3-DELETE": "on", "form-4-id": "", "form-4-first_name": "", diff --git a/tests/formsets/test_inline.py b/tests/formsets/test_inline.py index 6e355fc..dcc5f27 100644 --- a/tests/formsets/test_inline.py +++ b/tests/formsets/test_inline.py @@ -3,7 +3,10 @@ from django_sorcery.formsets import inlineformset_factory from ..base import TestCase -from ..testapp.models import Owner, Vehicle, VehicleType, db +from ..testapp.models import Owner +from ..testapp.models import Vehicle +from ..testapp.models import VehicleType +from ..testapp.models import db class TestInlineFormSet(TestCase): @@ -138,9 +141,7 @@ def test_inline_form_update_render(self): "

", ' ', ' ', - ' '.format( - self.owner.vehicles[0].id - ), + f' ', "

", "

", ' ', @@ -152,9 +153,7 @@ def test_inline_form_update_render(self): "

", ' ', ' ', - ' '.format( - self.owner.vehicles[1].id - ), + f' ', "

", "

", ' ', @@ -192,4 +191,5 @@ def test_inline_form_update_render(self): ] ) ) + self.assertHTMLEqual(soup.prettify(), expected_soup.prettify()) diff --git a/tests/management/commands/base.py b/tests/management/commands/base.py index a485c3f..22e4688 100644 --- a/tests/management/commands/base.py +++ b/tests/management/commands/base.py @@ -2,7 +2,6 @@ from ... import testapp - MIGRATION_DIR = os.path.abspath(os.path.join(os.path.dirname(testapp.__file__), "migrations")) diff --git a/tests/management/commands/test_sorcery.py b/tests/management/commands/test_sorcery.py index 89bf627..70b04f6 100644 --- a/tests/management/commands/test_sorcery.py +++ b/tests/management/commands/test_sorcery.py @@ -1,7 +1,9 @@ from django.test import TestCase + from django_sorcery.management.commands.sorcery import Command -from ...models_multidb import default_db, other_db +from ...models_multidb import default_db +from ...models_multidb import other_db class TestCreateAllDropAll(TestCase): diff --git a/tests/management/commands/test_sorcery_current.py b/tests/management/commands/test_sorcery_current.py index cc5b6f1..70b56aa 100644 --- a/tests/management/commands/test_sorcery_current.py +++ b/tests/management/commands/test_sorcery_current.py @@ -1,14 +1,14 @@ import six - from django.conf import settings from django.test import TestCase + from django_sorcery.db import databases from django_sorcery.management.commands.sorcery_current import Command from django_sorcery.management.commands.sorcery_downgrade import Downgrade from django_sorcery.management.commands.sorcery_upgrade import Upgrade -from .base import MIGRATION_DIR, MigrationMixin - +from .base import MIGRATION_DIR +from .base import MigrationMixin M1 = '''"""zero @@ -66,10 +66,10 @@ def setUp(self): super().setUp() for rev in ["000000000000", "000000000001"]: - self.delete_migration("{}_.py".format(rev)) + self.delete_migration(f"{rev}_.py") - self.write_migration(M1, "{}_.py".format("000000000000")) - self.write_migration(M2, "{}_.py".format("000000000001")) + self.write_migration(M1, "000000000000_.py") + self.write_migration(M2, "000000000001_.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) @@ -81,7 +81,7 @@ def tearDown(self): Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) for rev in ["000000000000", "000000000001"]: - self.delete_migration("{}_.py".format(rev)) + self.delete_migration(f"{rev}_.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) @@ -113,7 +113,7 @@ def test_verbose(self): f"Current revision(s) for postgresql://postgres:***@{settings.DB_URL.host}/test\n", "Rev: 000000000001 (head)\n", "Parent: 000000000000\n", - "Path: {}/000000000001_.py\n".format(MIGRATION_DIR), + f"Path: {MIGRATION_DIR}/000000000001_.py\n", "\n", " one\n", " \n", diff --git a/tests/management/commands/test_sorcery_downgrade.py b/tests/management/commands/test_sorcery_downgrade.py index 270dfd1..e4173d6 100644 --- a/tests/management/commands/test_sorcery_downgrade.py +++ b/tests/management/commands/test_sorcery_downgrade.py @@ -1,13 +1,12 @@ import six - from django.test import TestCase + from django_sorcery.db import databases from django_sorcery.management.commands.sorcery_downgrade import Downgrade from django_sorcery.management.commands.sorcery_upgrade import Upgrade from .base import MigrationMixin - M1 = '''"""zero Revision ID: 000000000000 @@ -63,8 +62,8 @@ class TestDowngrade(MigrationMixin, TestCase): def setUp(self): super().setUp() - self.write_migration(M1, "{}_.py".format("000000000000")) - self.write_migration(M2, "{}_.py".format("000000000001")) + self.write_migration(M1, "000000000000_.py") + self.write_migration(M2, "000000000001_.py") Upgrade().run_from_argv(["./manage.py sorcery", "upgrade", "tests_testapp", "--no-color"]) @@ -73,7 +72,7 @@ def tearDown(self): Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) for rev in ["000000000000", "000000000001"]: - self.delete_migration("{}_.py".format(rev)) + self.delete_migration(f"{rev}_.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) diff --git a/tests/management/commands/test_sorcery_heads.py b/tests/management/commands/test_sorcery_heads.py index 2ce4b5b..e2b6922 100644 --- a/tests/management/commands/test_sorcery_heads.py +++ b/tests/management/commands/test_sorcery_heads.py @@ -1,10 +1,10 @@ import six - from django.test import TestCase -from django_sorcery.management.commands.sorcery_heads import Command -from .base import MIGRATION_DIR, MigrationMixin +from django_sorcery.management.commands.sorcery_heads import Command +from .base import MIGRATION_DIR +from .base import MigrationMixin M1 = '''"""zero @@ -58,13 +58,13 @@ def downgrade(): class TestHeads(MigrationMixin, TestCase): def setUp(self): super().setUp() - self.write_migration(M1, "{}_.py".format("000000000000")) - self.write_migration(M2, "{}_.py".format("000000000001")) + self.write_migration(M1, "000000000000_.py") + self.write_migration(M2, "000000000001_.py") def tearDown(self): super().tearDown() for rev in ["000000000000", "000000000001"]: - self.delete_migration("{}_.py".format(rev)) + self.delete_migration(f"{rev}_.py") def test(self): out = six.StringIO() @@ -94,7 +94,7 @@ def test_verbose(self): "[tests_testapp]\n", "Rev: 000000000001 (head)\n", "Parent: 000000000000\n", - "Path: {}/000000000001_.py\n".format(MIGRATION_DIR), + f"Path: {MIGRATION_DIR}/000000000001_.py\n", "\n", " one\n", " \n", diff --git a/tests/management/commands/test_sorcery_history.py b/tests/management/commands/test_sorcery_history.py index c8c8138..527cb7a 100644 --- a/tests/management/commands/test_sorcery_history.py +++ b/tests/management/commands/test_sorcery_history.py @@ -1,11 +1,10 @@ import six - from django.test import TestCase + from django_sorcery.management.commands.sorcery_history import Command from .base import MigrationMixin - MIGRATION = '''"""zero Revision ID: 000000000000 diff --git a/tests/management/commands/test_sorcery_revision.py b/tests/management/commands/test_sorcery_revision.py index 75fbfe1..45fd8b9 100644 --- a/tests/management/commands/test_sorcery_revision.py +++ b/tests/management/commands/test_sorcery_revision.py @@ -1,15 +1,15 @@ import os import six - from django.test import TestCase + from django_sorcery.db import databases from django_sorcery.management.commands.sorcery_downgrade import Downgrade from django_sorcery.management.commands.sorcery_revision import Command from django_sorcery.management.commands.sorcery_upgrade import Upgrade -from .base import MIGRATION_DIR, MigrationMixin - +from .base import MIGRATION_DIR +from .base import MigrationMixin rev = "000000000000" @@ -23,8 +23,8 @@ def setUp(self): def tearDown(self): super().tearDown() - self.delete_migration("{}_.py".format(rev)) - self.delete_migration("{}_zero.py".format(rev)) + self.delete_migration(f"{rev}_.py") + self.delete_migration(f"{rev}_zero.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "--no-color"]) def test_without_app(self): @@ -52,7 +52,7 @@ def test_with_name(self): ["./manage.py sorcery", "makemigrations", "tests_testapp", "-r", rev, "-m", "zero", "--no-color"] ) - self.assertTrue(os.path.isfile(os.path.join(MIGRATION_DIR, "{}_zero.py".format(rev)))) + self.assertTrue(os.path.isfile(os.path.join(MIGRATION_DIR, f"{rev}_zero.py"))) def test_longer_version_table_identifier(self): out = six.StringIO() diff --git a/tests/management/commands/test_sorcery_stamp.py b/tests/management/commands/test_sorcery_stamp.py index d1c9033..0c18079 100644 --- a/tests/management/commands/test_sorcery_stamp.py +++ b/tests/management/commands/test_sorcery_stamp.py @@ -1,13 +1,12 @@ import six - from django.test import TestCase + from django_sorcery.db import databases from django_sorcery.management.commands.sorcery_downgrade import Downgrade from django_sorcery.management.commands.sorcery_stamp import Command from .base import MigrationMixin - M1 = '''"""zero Revision ID: 000000000000 @@ -39,14 +38,14 @@ class TestStamp(MigrationMixin, TestCase): def setUp(self): super().setUp() - self.delete_migration("{}_.py".format("000000000000")) - self.write_migration(M1, "{}_.py".format("000000000000")) + self.delete_migration("000000000000_.py") + self.write_migration(M1, "000000000000_.py") def tearDown(self): super().tearDown() Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "--no-color"]) - self.delete_migration("{}_.py".format("000000000000")) + self.delete_migration("000000000000_.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "--no-color"]) def test(self): diff --git a/tests/management/commands/test_sorcery_upgrade.py b/tests/management/commands/test_sorcery_upgrade.py index 8bc2af5..1891211 100644 --- a/tests/management/commands/test_sorcery_upgrade.py +++ b/tests/management/commands/test_sorcery_upgrade.py @@ -1,13 +1,12 @@ import six - from django.test import TestCase + from django_sorcery.db import databases from django_sorcery.management.commands.sorcery_downgrade import Downgrade from django_sorcery.management.commands.sorcery_upgrade import Command from .base import MigrationMixin - MIGRATION = '''"""zero Revision ID: {rev} @@ -39,15 +38,15 @@ class TestUpgrade(MigrationMixin, TestCase): def setUp(self): super().setUp() for rev in ["000000000000", "000000000001"]: - self.write_migration(MIGRATION.format(rev=rev), "{}_zero.py".format(rev)) - self.write_migration(MIGRATION.format(rev=rev), "{}_zero.py".format(rev)) + self.write_migration(MIGRATION.format(rev=rev), f"{rev}_zero.py") + self.write_migration(MIGRATION.format(rev=rev), f"{rev}_zero.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) def tearDown(self): super().tearDown() Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) for rev in ["000000000000", "000000000001"]: - self.delete_migration("{}_zero.py".format(rev)) + self.delete_migration(f"{rev}_zero.py") Downgrade().run_from_argv(["./manage.py sorcery", "downgrade", "tests_testapp", "--no-color"]) def test(self): diff --git a/tests/management/test_base.py b/tests/management/test_base.py index 730875f..315b1c8 100644 --- a/tests/management/test_base.py +++ b/tests/management/test_base.py @@ -1,6 +1,7 @@ from io import StringIO from django.core.management.base import BaseCommand + from django_sorcery.management import NamespacedCommand from ..base import TestCase diff --git a/tests/minimalapp/models.py b/tests/minimalapp/models.py index 47b33b3..83c652d 100644 --- a/tests/minimalapp/models.py +++ b/tests/minimalapp/models.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - db = databases.get("minimal") diff --git a/tests/models_backpop.py b/tests/models_backpop.py index 03186af..3c7c504 100644 --- a/tests/models_backpop.py +++ b/tests/models_backpop.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - db = databases.get("minimal_backpop") diff --git a/tests/models_multidb.py b/tests/models_multidb.py index 95f1d51..cecde1a 100644 --- a/tests/models_multidb.py +++ b/tests/models_multidb.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - default_db = databases.get("default") other_db = databases.get("fromdbs") diff --git a/tests/models_terrible_relations.py b/tests/models_terrible_relations.py index cdf903f..177393f 100644 --- a/tests/models_terrible_relations.py +++ b/tests/models_terrible_relations.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - db = databases.get("terrible") diff --git a/tests/otherapp/models.py b/tests/otherapp/models.py index 02ed4a5..d518123 100644 --- a/tests/otherapp/models.py +++ b/tests/otherapp/models.py @@ -1,6 +1,5 @@ from django_sorcery.db import databases - db = databases.get("test") diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index f89326e..08e6ad9 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,5 +1,6 @@ from django.core.exceptions import ValidationError from django.test import TestCase + from django_sorcery.exceptions import NestedValidationError diff --git a/tests/test_fields.py b/tests/test_fields.py index 6fc0e06..ce0d52b 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,14 +1,17 @@ import json from django.core.exceptions import ValidationError + from django_sorcery import fields -from django_sorcery.forms import ( - apply_limit_choices_to_form_field, - modelform_factory, -) +from django_sorcery.forms import apply_limit_choices_to_form_field +from django_sorcery.forms import modelform_factory from .base import TestCase -from .testapp.models import CompositePkModel, Owner, Vehicle, VehicleType, db +from .testapp.models import CompositePkModel +from .testapp.models import Owner +from .testapp.models import Vehicle +from .testapp.models import VehicleType +from .testapp.models import db class TestEnumField(TestCase): @@ -85,7 +88,8 @@ def test_field_not_required(self): class TestModelChoiceField(TestCase): def setUp(self): super().setUp() - db.add_all([Owner(first_name="first_name {}".format(i), last_name="last_name {}".format(i)) for i in range(10)]) + db.add_all([Owner(first_name=f"first_name {i}", last_name=f"last_name {i}") for i in range(10)]) + db.flush() def test_apply_limit_value(self): @@ -198,7 +202,8 @@ def test_get_bound_field(self): class TestModelMultipleChoiceField(TestCase): def setUp(self): super().setUp() - db.add_all([Owner(first_name="first_name {}".format(i), last_name="last_name {}".format(i)) for i in range(10)]) + db.add_all([Owner(first_name=f"first_name {i}", last_name=f"last_name {i}") for i in range(10)]) + db.flush() def test_to_python(self): diff --git a/tests/test_forms.py b/tests/test_forms.py index 9fc5eb8..6dd0d1c 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -1,21 +1,22 @@ from django import forms as djangoforms -from django.core.exceptions import ImproperlyConfigured, ValidationError +from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ValidationError from django.forms import fields as djangofields -from django_sorcery import fields as sorceryfields, forms + +from django_sorcery import fields as sorceryfields +from django_sorcery import forms from .base import TestCase -from .testapp.models import ( - ClassicModel, - ModelFullCleanFail, - ModelOne, - ModelTwo, - Option, - Owner, - Part, - Vehicle, - VehicleType, - db, -) +from .testapp.models import ClassicModel +from .testapp.models import ModelFullCleanFail +from .testapp.models import ModelOne +from .testapp.models import ModelTwo +from .testapp.models import Option +from .testapp.models import Owner +from .testapp.models import Part +from .testapp.models import Vehicle +from .testapp.models import VehicleType +from .testapp.models import db class TestFieldsForModel(TestCase): @@ -305,7 +306,7 @@ def test_modelform_factory_new_render(self): ' ', ' ", "

", "

", @@ -339,6 +340,7 @@ def test_modelform_factory_new_render(self): ] ) ) + self.assertEqual(soup.prettify(), expected_soup.prettify()) def test_modelform_factory_instance_render(self): @@ -403,7 +405,7 @@ def test_modelform_factory_instance_render(self): ' ', ' ", "

", "

", diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 93fcdcc..90b7df8 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -1,8 +1,12 @@ import pytest -from django_sorcery.pytest_plugin import sqlalchemy_profiler, transact # noqa + +from django_sorcery.pytest_plugin import sqlalchemy_profiler # noqa +from django_sorcery.pytest_plugin import transact # noqa from django_sorcery.testing import CommitException -from .testapp.models import Business, Owner, db +from .testapp.models import Business +from .testapp.models import Owner +from .testapp.models import db def test_profiler(sqlalchemy_profiler): # noqa diff --git a/tests/test_routers.py b/tests/test_routers.py index 230d00f..1ebf214 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -1,6 +1,10 @@ from django.core.exceptions import ImproperlyConfigured + from django_sorcery import viewsets -from django_sorcery.routers import BaseRouter, Route, SimpleRouter, action +from django_sorcery.routers import BaseRouter +from django_sorcery.routers import Route +from django_sorcery.routers import SimpleRouter +from django_sorcery.routers import action from .base import TestCase from .testapp.models import Owner diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 18d3a8f..5a6b569 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,9 +1,11 @@ from django.core.exceptions import ImproperlyConfigured from django.http import Http404 + from django_sorcery import shortcuts from .base import TestCase -from .testapp.models import Owner, db +from .testapp.models import Owner +from .testapp.models import db class TestShortcuts(TestCase): diff --git a/tests/test_utils.py b/tests/test_utils.py index 91f24a7..3c3f86c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,5 @@ from django.test import TestCase + from django_sorcery import utils diff --git a/tests/test_validators.py b/tests/test_validators.py index 80c04ff..9af70aa 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,19 +1,19 @@ from collections import namedtuple from django.core.exceptions import ValidationError + from django_sorcery.exceptions import NestedValidationError -from django_sorcery.validators import ( - ValidateCantRemove, - ValidateEmptyWhen, - ValidateNotEmptyWhen, - ValidateOnlyOneOf, - ValidateTogetherModelFields, - ValidateValue, - ValidationRunner, -) +from django_sorcery.validators import ValidateCantRemove +from django_sorcery.validators import ValidateEmptyWhen +from django_sorcery.validators import ValidateNotEmptyWhen +from django_sorcery.validators import ValidateOnlyOneOf +from django_sorcery.validators import ValidateTogetherModelFields +from django_sorcery.validators import ValidateValue +from django_sorcery.validators import ValidationRunner from .base import TestCase -from .testapp.models import ValidateUniqueModel, db +from .testapp.models import ValidateUniqueModel +from .testapp.models import db class TestValidatorRunner(TestCase): diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 12b5c8e..42aa641 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -2,14 +2,13 @@ from django.core.exceptions import ValidationError from django.core.validators import RegexValidator + from django_sorcery.db import databases -from django_sorcery.db.models import autocoerce, autocoerce_properties +from django_sorcery.db.models import autocoerce +from django_sorcery.db.models import autocoerce_properties from django_sorcery.db.query import Query -from django_sorcery.validators import ( - ValidateTogetherModelFields, - ValidateUnique, -) - +from django_sorcery.validators import ValidateTogetherModelFields +from django_sorcery.validators import ValidateUnique db = databases.get("test") diff --git a/tests/testapp/views.py b/tests/testapp/views.py index 4b11079..5dd201d 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -1,8 +1,13 @@ from django.urls import reverse_lazy -from django_sorcery import forms, views, viewsets + +from django_sorcery import forms +from django_sorcery import views +from django_sorcery import viewsets from django_sorcery.routers import action -from .models import Owner, Vehicle, db +from .models import Owner +from .models import Vehicle +from .models import db class OwnerListView(views.ListView): diff --git a/tests/urls.py b/tests/urls.py index 56c2cce..3669623 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -2,12 +2,12 @@ from .testapp import views - try: - from django.conf.urls import url as re_path from django.conf.urls import include + from django.conf.urls import url as re_path except ImportError: # pragma: no cover - from django.urls import re_path, include + from django.urls import include + from django.urls import re_path router = routers.SimpleRouter() diff --git a/tests/views/test_base.py b/tests/views/test_base.py index e5f52bf..dcf59ce 100644 --- a/tests/views/test_base.py +++ b/tests/views/test_base.py @@ -1,9 +1,12 @@ import sqlalchemy as sa from django.core.exceptions import ImproperlyConfigured from django.test import TestCase + from django_sorcery.views.base import SQLAlchemyMixin -from ..testapp.models import ClassicModel, Owner, db +from ..testapp.models import ClassicModel +from ..testapp.models import Owner +from ..testapp.models import db class TestBaseView(TestCase): diff --git a/tests/views/test_detail.py b/tests/views/test_detail.py index 94ee5d9..65213de 100644 --- a/tests/views/test_detail.py +++ b/tests/views/test_detail.py @@ -1,13 +1,13 @@ from django.core.exceptions import ImproperlyConfigured from django.urls import reverse from django.utils.html import escape -from django_sorcery.views.detail import ( - SingleObjectMixin, - SingleObjectTemplateResponseMixin, -) + +from django_sorcery.views.detail import SingleObjectMixin +from django_sorcery.views.detail import SingleObjectTemplateResponseMixin from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db class TestDetailView(TestCase): @@ -44,7 +44,10 @@ def test_detail_generic(self): response = self.client.get(url) - self.assertEqual(response.content.decode().strip(), "owner" + escape(repr(Owner.query.get(1)))) + self.assertEqual( + response.content.decode().strip(), + f"owner{escape(repr(Owner.query.get(1)))}", + ) def test_detail_generic_with_template(self): @@ -60,7 +63,10 @@ def test_detail_generic_with_slug(self): response = self.client.get(url) - self.assertEqual(response.content.decode().strip(), "owner" + escape(repr(Owner.query.get(1)))) + self.assertEqual( + response.content.decode().strip(), + f"owner{escape(repr(Owner.query.get(1)))}", + ) def test_detail_generic_404(self): diff --git a/tests/views/test_edit.py b/tests/views/test_edit.py index 6b0bb98..4aa1680 100644 --- a/tests/views/test_edit.py +++ b/tests/views/test_edit.py @@ -1,10 +1,12 @@ from django.core.exceptions import ImproperlyConfigured from django.urls import reverse + from django_sorcery import forms from django_sorcery.views import edit from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db from ..testapp.views import OwnerCreateViewWithForm diff --git a/tests/views/test_list.py b/tests/views/test_list.py index d4ce0fd..0e8266f 100644 --- a/tests/views/test_list.py +++ b/tests/views/test_list.py @@ -2,13 +2,13 @@ from django.core.exceptions import ImproperlyConfigured from django.urls import reverse from django.utils.html import escape -from django_sorcery.views.list import ( - MultipleObjectMixin, - MultipleObjectTemplateResponseMixin, -) + +from django_sorcery.views.list import MultipleObjectMixin +from django_sorcery.views.list import MultipleObjectTemplateResponseMixin from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db class TestListView(TestCase): @@ -62,7 +62,8 @@ def test_list_ordering(self): ) def test_list_pagination(self): - db.add_all([Owner(id=i, first_name="Test {}".format(i), last_name="Owner {}".format(i)) for i in range(5, 50)]) + db.add_all([Owner(id=i, first_name=f"Test {i}", last_name=f"Owner {i}") for i in range(5, 50)]) + db.flush() url = reverse("owners_list") @@ -75,12 +76,13 @@ def test_list_pagination(self): ) def test_list_pagination_last_page(self): - db.add_all([Owner(id=i, first_name="Test {}".format(i), last_name="Owner {}".format(i)) for i in range(5, 50)]) + db.add_all([Owner(id=i, first_name=f"Test {i}", last_name=f"Owner {i}") for i in range(5, 50)]) + db.flush() url = reverse("owners_list") - response = self.client.get("{}?page=last".format(url)) + response = self.client.get(f"{url}?page=last") self.assertEqual( response.content.decode().strip(), @@ -88,22 +90,24 @@ def test_list_pagination_last_page(self): ) def test_list_pagination_bad_page(self): - db.add_all([Owner(id=i, first_name="Test {}".format(i), last_name="Owner {}".format(i)) for i in range(5, 50)]) + db.add_all([Owner(id=i, first_name=f"Test {i}", last_name=f"Owner {i}") for i in range(5, 50)]) + db.flush() url = reverse("owners_list") - response = self.client.get("{}?page=abc".format(url)) + response = self.client.get(f"{url}?page=abc") self.assertEqual(response.status_code, 404) def test_list_pagination_bad_page_out_of_range(self): - db.add_all([Owner(id=i, first_name="Test {}".format(i), last_name="Owner {}".format(i)) for i in range(5, 50)]) + db.add_all([Owner(id=i, first_name=f"Test {i}", last_name=f"Owner {i}") for i in range(5, 50)]) + db.flush() url = reverse("owners_list") - response = self.client.get("{}?page=100".format(url)) + response = self.client.get(f"{url}?page=100") self.assertEqual(response.status_code, 404) diff --git a/tests/viewsets/test_mixins.py b/tests/viewsets/test_mixins.py index b069727..45522af 100644 --- a/tests/viewsets/test_mixins.py +++ b/tests/viewsets/test_mixins.py @@ -1,10 +1,13 @@ from django.core.exceptions import ImproperlyConfigured from django.http import Http404 from django.urls import reverse -from django_sorcery import forms, viewsets + +from django_sorcery import forms +from django_sorcery import viewsets from ..base import TestCase -from ..testapp.models import Owner, db +from ..testapp.models import Owner +from ..testapp.models import db class TestListModelMixin(TestCase): diff --git a/tox.ini b/tox.ini index 14cbdee..ecf9510 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,9 @@ toxworkdir={env:TOX_WORK_DIR:.tox} skipsdist = true envlist = lint - pypy36-{sqla12,sqla13,sqla14}-{dj11,dj20,dj21,dj22,dj30,dj31,dj32 } pypy37-{sqla12,sqla13,sqla14}-{dj11,dj20,dj21,dj22,dj30,dj31,dj32 } pypy38-{sqla12,sqla13,sqla14}-{ dj20,dj21,dj22,dj30,dj31,dj32,dj40,dj41} - py36-{sqla12,sqla13,sqla14}-{dj11,dj20,dj21,dj22,dj30,dj31,dj32 } py37-{sqla12,sqla13,sqla14}-{ dj20,dj21,dj22,dj30,dj31,dj32 } py38-{sqla12,sqla13,sqla14}-{ dj20,dj21,dj22,dj30,dj31,dj32,dj40,dj41} py39-{sqla12,sqla13,sqla14}-{ dj20,dj21,dj22,dj30,dj31,dj32,dj40,dj41} @@ -24,7 +22,6 @@ passenv = CPPFLAGS DATABASE_URL basepython = - py36: python3.6 py37: python3.7 py38: python3.8 py39: python3.9