Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 11 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
---
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:
- id: pyupgrade
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
Expand Down
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ include *.txt
include *.yaml *.yml
include LICENSE
include Makefile
include pytest.ini
include tox.ini
include Dockerfile
exclude .*
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
15 changes: 2 additions & 13 deletions django_sorcery/__init__.py
Original file line number Diff line number Diff line change
@@ -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("."))
4 changes: 0 additions & 4 deletions django_sorcery/__version__.py

This file was deleted.

1 change: 0 additions & 1 deletion django_sorcery/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,4 @@
from .sqlalchemy import SQLAlchemy # noqa
from .utils import dbdict


databases = dbdict()
8 changes: 2 additions & 6 deletions django_sorcery/db/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from .url import DIALECT_MAP_TO_DJANGO


__all__ = [
"BigIntegerField",
"BinaryField",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
)
Expand Down
12 changes: 4 additions & 8 deletions django_sorcery/db/meta/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
25 changes: 12 additions & 13 deletions django_sorcery/db/meta/model.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,7 +17,6 @@
from .composite import composite_info
from .relations import relation_info


Identity = namedtuple("Key", ["model", "pk"])
inflector = inflect.engine()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -147,10 +148,11 @@ def __getattr__(self, name):

def __repr__(self):
reprs = ["<model_info({!s})>".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
Expand Down Expand Up @@ -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``"""
Expand Down
18 changes: 8 additions & 10 deletions django_sorcery/db/meta/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[:]
Expand All @@ -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 "<relation_info({}.{})>".format(self.parent_model.__name__, self.name)
return f"<relation_info({self.parent_model.__name__}.{self.name})>"
1 change: 0 additions & 1 deletion django_sorcery/db/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
12 changes: 7 additions & 5 deletions django_sorcery/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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


Expand Down
13 changes: 8 additions & 5 deletions django_sorcery/db/profiler.py
Original file line number Diff line number Diff line change
@@ -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"}

Expand Down Expand Up @@ -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()),
)
Loading