From aab46de800b5bd26e426ddd3cbd8a16cb7bcdf7a Mon Sep 17 00:00:00 2001 From: collerek Date: Mon, 3 Jan 2022 18:23:22 +0100 Subject: [PATCH 01/10] remove date dumping to isoformat, add pydantic 1.9 support --- Makefile | 3 - ormar/models/helpers/validation.py | 5 +- ormar/models/newbasemodel.py | 1 - ormar/queryset/actions/filter_action.py | 20 -- poetry.lock | 296 ++++++++++-------- pyproject.toml | 4 +- .../test_dates_with_timezone.py | 24 ++ 7 files changed, 193 insertions(+), 160 deletions(-) diff --git a/Makefile b/Makefile index c3ca423..5aa089d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,3 @@ -PIPENV_RUN := pipenv run -PG_DOCKERFILE_NAME := fastapi-users-test-mongo - test_all: test_pg test_mysql test_sqlite test_pg: export DATABASE_URL=postgresql://username:password@localhost:5432/testsuite diff --git a/ormar/models/helpers/validation.py b/ormar/models/helpers/validation.py index fb66b71..d25837d 100644 --- a/ormar/models/helpers/validation.py +++ b/ormar/models/helpers/validation.py @@ -20,7 +20,6 @@ except ImportError: # pragma: no cover import pydantic from pydantic.class_validators import make_generic_validator from pydantic.fields import ModelField, SHAPE_LIST -from pydantic.main import SchemaExtraCallable import ormar # noqa: I100, I202 from ormar.models.helpers.models import meta_field_not_set @@ -249,7 +248,7 @@ def overwrite_binary_format(schema: Dict[str, Any], model: Type["Model"]) -> Non ] -def construct_modify_schema_function(fields_with_choices: List) -> SchemaExtraCallable: +def construct_modify_schema_function(fields_with_choices: List) -> Callable: """ Modifies the schema to include fields with choices validator. Those fields will be displayed in schema as Enum types with available choices @@ -275,7 +274,7 @@ def construct_modify_schema_function(fields_with_choices: List) -> SchemaExtraCa return staticmethod(schema_extra) # type: ignore -def construct_schema_function_without_choices() -> SchemaExtraCallable: +def construct_schema_function_without_choices() -> Callable: """ Modifies model example and description if needed. diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index eb0805c..d05498e 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -76,7 +76,6 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass pk: Any __model_fields__: Dict[str, BaseField] __table__: sqlalchemy.Table - __fields__: Dict[str, pydantic.fields.ModelField] __pydantic_model__: Type[BaseModel] __pkname__: str __tablename__: str diff --git a/ormar/queryset/actions/filter_action.py b/ormar/queryset/actions/filter_action.py index e23a563..443b36f 100644 --- a/ormar/queryset/actions/filter_action.py +++ b/ormar/queryset/actions/filter_action.py @@ -1,4 +1,3 @@ -import datetime from typing import Any, Dict, TYPE_CHECKING, Type import sqlalchemy @@ -138,8 +137,6 @@ class FilterAction(QueryAction): if isinstance(self.filter_value, ormar.Model): self.filter_value = self.filter_value.pk - self._convert_dates_if_required() - op_attr = FILTER_OPERATORS[self.operator] if self.operator == "isnull": op_attr = "is_" if self.filter_value else "isnot" @@ -152,23 +149,6 @@ class FilterAction(QueryAction): ) return clause - def _convert_dates_if_required(self) -> None: - """ - Converts dates, time and datetime to isoformat - """ - if isinstance( - self.filter_value, (datetime.date, datetime.time, datetime.datetime) - ): - self.filter_value = self.filter_value.isoformat() - - if isinstance(self.filter_value, (list, tuple, set)): - self.filter_value = [ - x.isoformat() - if isinstance(x, (datetime.date, datetime.time, datetime.datetime)) - else x - for x in self.filter_value - ] - def _compile_clause( self, clause: sqlalchemy.sql.expression.BinaryExpression, modifiers: Dict ) -> sqlalchemy.sql.expression.TextClause: diff --git a/poetry.lock b/poetry.lock index c37cdba..77312ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -82,14 +82,14 @@ typed = ["typed-ast"] [[package]] name = "async-timeout" -version = "4.0.1" +version = "4.0.2" description = "Timeout context manager for asyncio programs" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -typing-extensions = ">=3.6.5" +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} [[package]] name = "asyncpg" @@ -117,32 +117,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "backports.entry-points-selectable" -version = "1.1.1" -description = "Compatibility shim providing selectable entry points for older implementations" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "bandit" @@ -336,7 +321,7 @@ sqlite = ["aiosqlite"] [[package]] name = "databind.core" -version = "1.2.4" +version = "1.2.6" description = "Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses. Compatible with Python 3.7 and newer." category = "dev" optional = false @@ -347,6 +332,7 @@ Deprecated = ">=1.2.12,<2.0.0" "nr.optional" = ">=0.1.1,<1.0.0" "nr.parsing.date" = ">=1.0.1,<2.0.0" "nr.preconditions" = ">=0.0.4,<1.0.0" +"nr.pylang.utils" = ">=0.1.3,<1.0.0" "nr.stream" = ">=0.1.1,<1.0.0" typing-extensions = ">=3.10.0,<4.0.0" @@ -355,14 +341,14 @@ test = ["types-dataclasses", "types-deprecated", "types-pkg-resources"] [[package]] name = "databind.json" -version = "1.2.4" +version = "1.2.6" description = "De-/serialize Python dataclasses to or from JSON payloads. Compatible with Python 3.7 and newer." category = "dev" optional = false python-versions = ">=3.7.0,<4.0.0" [package.dependencies] -"databind.core" = ">=1.2.4,<2.0.0" +"databind.core" = ">=1.2.6,<2.0.0" "nr.optional" = ">=0.1.1,<1.0.0" "nr.parsing.date" = ">=1.0.1,<2.0.0" "nr.preconditions" = ">=0.0.4,<1.0.0" @@ -459,7 +445,7 @@ test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.91 [[package]] name = "filelock" -version = "3.4.0" +version = "3.4.1" description = "A platform independent file lock." category = "dev" optional = false @@ -654,7 +640,7 @@ docs = ["sphinx"] [[package]] name = "identify" -version = "2.4.0" +version = "2.4.1" description = "File identification library for Python" category = "dev" optional = false @@ -802,7 +788,7 @@ i18n = ["babel (>=2.9.0)"] [[package]] name = "mkdocs-material" -version = "8.1.2" +version = "8.1.4" description = "A Material Design theme for MkDocs" category = "dev" optional = false @@ -1033,7 +1019,7 @@ virtualenv = ">=20.0.8" [[package]] name = "psycopg2-binary" -version = "2.9.2" +version = "2.9.3" description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" optional = false @@ -1065,7 +1051,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" -version = "1.8.2" +version = "1.9.0" description = "Data validation and settings management using python 3.6 type hinting" category = "main" optional = false @@ -1111,7 +1097,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.1" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -1233,7 +1219,7 @@ pyyaml = "*" [[package]] name = "requests" -version = "2.26.0" +version = "2.27.0" description = "Python HTTP for Humans." category = "dev" optional = false @@ -1374,7 +1360,7 @@ python-versions = "*" [[package]] name = "types-aiofiles" -version = "0.7.0" +version = "0.7.3" description = "Typing stubs for aiofiles" category = "dev" optional = false @@ -1382,7 +1368,7 @@ python-versions = "*" [[package]] name = "types-cryptography" -version = "3.3.9" +version = "3.3.10" description = "Typing stubs for cryptography" category = "dev" optional = false @@ -1394,7 +1380,7 @@ types-ipaddress = "*" [[package]] name = "types-dataclasses" -version = "0.6.1" +version = "0.6.2" description = "Typing stubs for dataclasses" category = "dev" optional = false @@ -1434,7 +1420,7 @@ python-versions = "*" [[package]] name = "types-pymysql" -version = "1.0.6" +version = "1.0.8" description = "Typing stubs for PyMySQL" category = "dev" optional = false @@ -1442,7 +1428,7 @@ python-versions = "*" [[package]] name = "types-requests" -version = "2.26.1" +version = "2.26.3" description = "Typing stubs for requests" category = "dev" optional = false @@ -1487,14 +1473,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.10.0" +version = "20.13.0" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -1560,7 +1545,7 @@ sqlite = [] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "878132e71b738d73cb9345d2ed0892e1f3105df8ef0e50392b38096268b9f837" +content-hash = "1b262c7c15f662e22a5f14061226a73ea82e7923e9d648c1a950119ac6216ce4" [metadata.files] aiocontextvars = [ @@ -1588,8 +1573,8 @@ astpretty = [ {file = "astpretty-2.1.0.tar.gz", hash = "sha256:8a801fcda604ec741f010bb36d7cbadc3ec8a182ea6fb83e20ab663463e75ff6"}, ] async-timeout = [ - {file = "async-timeout-4.0.1.tar.gz", hash = "sha256:b930cb161a39042f9222f6efb7301399c87eeab394727ec5437924a36d6eef51"}, - {file = "async_timeout-4.0.1-py3-none-any.whl", hash = "sha256:a22c0b311af23337eb05fcf05a8b51c3ea53729d46fb5460af62bee033cec690"}, + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] asyncpg = [ {file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"}, @@ -1624,12 +1609,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -"backports.entry-points-selectable" = [ - {file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"}, - {file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] bandit = [ {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, @@ -1802,12 +1783,12 @@ databases = [ {file = "databases-0.5.3.tar.gz", hash = "sha256:b69d74ee0b47fa30bb6e76db0c58da998e973393259d29215d8fb29352162bd6"}, ] "databind.core" = [ - {file = "databind.core-1.2.4-py3-none-any.whl", hash = "sha256:867a91ee58e7b484e7f765b6d51735d737e6e6deb4b1a2409552f055a1962ea8"}, - {file = "databind.core-1.2.4.tar.gz", hash = "sha256:9e644b4f3d3573239e5994c07d86fe606e6adf60420318a3525582e7b844de5e"}, + {file = "databind.core-1.2.6-py3-none-any.whl", hash = "sha256:aaf0b817cbcd8b0797485d2a762075146b64ee43287af42b9077ff9b7a981875"}, + {file = "databind.core-1.2.6.tar.gz", hash = "sha256:bd07c51bb19c772d731ab22998b0285454a9594cd803d3300900164751bd4bc0"}, ] "databind.json" = [ - {file = "databind.json-1.2.4-py3-none-any.whl", hash = "sha256:4b0a522e68a67aee34ba5bbddac0218be376ba80849571953ca46e9efb9e85c2"}, - {file = "databind.json-1.2.4.tar.gz", hash = "sha256:0b5d74b8a484aa9968c0cfff6609d97e67314f6a4d50a587a93a19301d439e81"}, + {file = "databind.json-1.2.6-py3-none-any.whl", hash = "sha256:9b729e0056135fbf97d382f589d6341bd5d3b6bb04d40c72db71f71c6c69cb52"}, + {file = "databind.json-1.2.6.tar.gz", hash = "sha256:50134de32c303b618ab67d251aa456f095101b2d8cddb347faf9eaeb1e984468"}, ] dataclasses = [ {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, @@ -1837,8 +1818,8 @@ fastapi = [ {file = "fastapi-0.70.1.tar.gz", hash = "sha256:21d03979b5336375c66fa5d1f3126c6beca650d5d2166fbb78345a30d33c8d06"}, ] filelock = [ - {file = "filelock-3.4.0-py3-none-any.whl", hash = "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8"}, - {file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"}, + {file = "filelock-3.4.1-py3-none-any.whl", hash = "sha256:a4bc51381e01502a30e9f06dd4fa19a1712eab852b6fb0f84fd7cce0793d8ca3"}, + {file = "filelock-3.4.1.tar.gz", hash = "sha256:0f12f552b42b5bf60dba233710bf71337d35494fc8bdd4fd6d9f6d082ad45e06"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -1904,6 +1885,7 @@ greenlet = [ {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, @@ -1916,6 +1898,7 @@ greenlet = [ {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, @@ -1924,6 +1907,7 @@ greenlet = [ {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, @@ -1932,6 +1916,7 @@ greenlet = [ {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, @@ -1940,13 +1925,14 @@ greenlet = [ {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] identify = [ - {file = "identify-2.4.0-py2.py3-none-any.whl", hash = "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc"}, - {file = "identify-2.4.0.tar.gz", hash = "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107"}, + {file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"}, + {file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -2007,6 +1993,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, @@ -2018,6 +2007,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2029,6 +2021,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, @@ -2041,6 +2036,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2053,6 +2051,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2070,8 +2071,8 @@ mkdocs = [ {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, ] mkdocs-material = [ - {file = "mkdocs-material-8.1.2.tar.gz", hash = "sha256:83b73d62b11cbc97c3b05de44f2fad3b96edf3fe2cf977851972e2c25348893b"}, - {file = "mkdocs_material-8.1.2-py2.py3-none-any.whl", hash = "sha256:2fe84abc86daa3cb5b5e7e0438c20c6e2a99c1f4a7527c783b0f8b1fb4fb840a"}, + {file = "mkdocs-material-8.1.4.tar.gz", hash = "sha256:b310be0f69503214406afe2e0495449f135503fe084467b607f860f4ed01f09e"}, + {file = "mkdocs_material-8.1.4-py2.py3-none-any.whl", hash = "sha256:6d615457ae18fd6e6a890a6f70d5681f3a6b931da67c2de7df1b273f8f702b4d"}, ] mkdocs-material-extensions = [ {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, @@ -2200,42 +2201,62 @@ pre-commit = [ {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, ] psycopg2-binary = [ - {file = "psycopg2-binary-2.9.2.tar.gz", hash = "sha256:234b1f48488b2f86aac04fb00cb04e5e9bcb960f34fa8a8e41b73149d581a93b"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c0e1fb7097ded2cc44d9037cfc68ad86a30341261492e7de95d180e534969fb2"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:717525cdc97b23182ff6f470fb5bf6f0bc796b5a7000c6f6699d6679991e4a5e"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3865d0cd919349c45603bd7e80249a382c5ecf8106304cfd153282adf9684b6a"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:daf6b5c62eb738872d61a1fa740d7768904911ba5a7e055ed72169d379b58beb"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:3ac83656ff4fbe7f2a956ab085e3eb1d678df54759965d509bdd6a06ce520d49"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-win32.whl", hash = "sha256:a04cfa231e7d9b63639e62166a4051cb47ca599fa341463fa3e1c48585fcee64"}, - {file = "psycopg2_binary-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:c6e16e085fe6dc6c099ee0be56657aa9ad71027465ef9591d302ba230c404c7e"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:53912199abb626a7249c662e72b70b4f57bf37f840599cec68625171435790dd"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:029e09a892b9ebc3c77851f69ce0720e1b72a9c6850460cee49b14dfbf9ccdd2"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db1b03c189f85b8df29030ad32d521dd7dcb862fd5f8892035314f5b886e70ce"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:2eecbdc5fa5886f2dd6cc673ce4291cc0fb8900965315268960ad9c2477f8276"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:a77e98c68b0e6c51d4d6a994d22b30e77276cbd33e4aabdde03b9ad3a2c148aa"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:bf31e6fdb4ec1f6d98a07f48836508ed6edd19b48b13bbf168fbc1bd014b4ca2"}, - {file = "psycopg2_binary-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f9c37ecb173d76cf49e519133fd70851b8f9c38b6b8c1cb7fcfc71368d4cc6fc"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:a507db7758953b1b170c4310691a1a89877029b1e11b08ba5fc8ae3ddb35596b"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e4bbcfb403221ea1953f3e0a85cef00ed15c1683a66cf35c956a7e37c33a4c4"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4dff0f15af6936c6fe6da7067b4216edbbe076ad8625da819cc066591b1133c"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:8d2aafe46eb87742425ece38130510fbb035787ee89a329af299029c4d9ae318"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:37c8f00f7a2860bac9f7a54f03c243fc1dd9b367e5b2b52f5a02e5f4e9d8c49b"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:ef97578fab5115e3af4334dd3376dea3c3a79328a3314b21ec7ced02920b916d"}, - {file = "psycopg2_binary-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7e6bd4f532c2cd297b81114526176b240109a1c52020adca69c3f3226c65dc18"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:eeee7b18c51d02e49bf1984d7af26e8843fe68e31fa1cbab5366ebdfa1c89ade"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:497372cc76e6cbce2f51b37be141f360a321423c03eb9be45524b1d123f4cd11"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671699aff57d22a245b7f4bba89e3de97dc841c5e98bd7f685429b2b20eca47"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:b9d45374ba98c1184df9cce93a0b766097544f8bdfcd5de83ff10f939c193125"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:a1852c5bef7e5f52bd43fde5eda610d4df0fb2efc31028150933e84b4140d47a"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-win32.whl", hash = "sha256:108b0380969ddab7c8ef2a813a57f87b308b2f88ec15f1a1e7b653964a3cfb25"}, - {file = "psycopg2_binary-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:14427437117f38e65f71db65d8eafd0e86837be456567798712b8da89db2b2dd"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:578c279cd1ce04f05ae0912530ece00bab92854911808e5aec27588aba87e361"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2dea4deac3dd3687e32daeb0712ee96c535970dfdded37a11de6a21145ab0e"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b592f09ff18cfcc9037b9a976fcd62db48cae9dbd5385f2471d4c2ba40c52b4d"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:3a320e7a804f3886a599fea507364aaafbb8387027fffcdfbd34d96316c806c7"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7585ca73dcfe326f31fafa8f96e6bb98ea9e9e46c7a1924ec8101d797914ae27"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-win32.whl", hash = "sha256:9c0aaad07941419926b9bd00171e49fe6b06e42e5527fb91671e137fe6c93d77"}, - {file = "psycopg2_binary-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa2847d8073951dbc84c4f8b32c620764db3c2eb0d99a04835fecfab7d04816e"}, + {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2250,28 +2271,41 @@ pycparser = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pydantic = [ - {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, - {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, - {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, - {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, - {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, - {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, - {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, - {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, - {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, - {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, + {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, + {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, + {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, + {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, + {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, + {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, + {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, + {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, + {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, ] pydoc-markdown = [ {file = "pydoc-markdown-4.5.0.tar.gz", hash = "sha256:131636ed32324d255816e476d72eb592542f120fce0d9a4ddca888934bc51282"}, @@ -2282,8 +2316,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.1-py3-none-any.whl", hash = "sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c"}, + {file = "Pygments-2.11.1.tar.gz", hash = "sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4"}, ] pymdown-extensions = [ {file = "pymdown-extensions-9.1.tar.gz", hash = "sha256:74247f2c80f1d9e3c7242abe1c16317da36c6f26c7ad4b8a7f457f0ec20f0365"}, @@ -2349,8 +2383,8 @@ pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.0-py2.py3-none-any.whl", hash = "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"}, + {file = "requests-2.27.0.tar.gz", hash = "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -2454,16 +2488,16 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] types-aiofiles = [ - {file = "types-aiofiles-0.7.0.tar.gz", hash = "sha256:f1c9aacd8ee8fcf3c66744b4bd0963e1eac3753f0e5c20d71214f53da11bc7e3"}, - {file = "types_aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:0334df97d4fcc70a29ad82ccbe48b914cd7bf31e6cc95942862057892ec5cc81"}, + {file = "types-aiofiles-0.7.3.tar.gz", hash = "sha256:5c54783745252302f5ba3e14d8f7e8547d100f72c74c9a5478ce2f7f01aa0cda"}, + {file = "types_aiofiles-0.7.3-py3-none-any.whl", hash = "sha256:fd7f410a94e0362c7a3cf7a98e62f972f1dab245130dfdf6f48914f462994262"}, ] types-cryptography = [ - {file = "types-cryptography-3.3.9.tar.gz", hash = "sha256:39b8ce64fe89a7e31ea49030ee19ad81724c38ca7283e2c1f91b2ecfeea8b102"}, - {file = "types_cryptography-3.3.9-py3-none-any.whl", hash = "sha256:2127dda3016ba9a6e518a76dec46a009a7da112ace79fc6b590fd028fa24db6b"}, + {file = "types-cryptography-3.3.10.tar.gz", hash = "sha256:b5610287f57c68dabb9123bb76384bcacbf64d0d8282004a2434b84c7aaf9a0e"}, + {file = "types_cryptography-3.3.10-py3-none-any.whl", hash = "sha256:fac6e1cefb700f2c072b7bdcc52ee85b7f9e054a907ce0d56c8a3e2bb7bacbff"}, ] types-dataclasses = [ - {file = "types-dataclasses-0.6.1.tar.gz", hash = "sha256:6568532fed11f854e4db2eb48063385b323b93ecadd09f10a215d56246c306d7"}, - {file = "types_dataclasses-0.6.1-py3-none-any.whl", hash = "sha256:aa45bb0dacdba09e3195a36ff8337bba45eac03b6f31c4645e87b4a2a47830dd"}, + {file = "types-dataclasses-0.6.2.tar.gz", hash = "sha256:d74876d7a5e39ae6285765dd871e3b85d8186251f7b48e18130a35024c78bf73"}, + {file = "types_dataclasses-0.6.2-py3-none-any.whl", hash = "sha256:19ea746937b82bfa0d3bba1dd78858eca2ed81932eae888c3f2966aa3fd0aec6"}, ] types-enum34 = [ {file = "types-enum34-1.1.1.tar.gz", hash = "sha256:55c44c44f193636ed82f1cb68a9a632e1ea7096724f024c25e015976809df339"}, @@ -2482,12 +2516,12 @@ types-pkg-resources = [ {file = "types_pkg_resources-0.1.3-py2.py3-none-any.whl", hash = "sha256:0cb9972cee992249f93fff1a491bf2dc3ce674e5a1926e27d4f0866f7d9b6d9c"}, ] types-pymysql = [ - {file = "types-PyMySQL-1.0.6.tar.gz", hash = "sha256:2f43b8ba9357c0b158cba26b58b36951754db45b8c8e7d301d0208de598cb7d6"}, - {file = "types_PyMySQL-1.0.6-py3-none-any.whl", hash = "sha256:0589f4076bb37dbc3c95de14aa4b66c919b220cb3437ef7775c184ac7b27807b"}, + {file = "types-PyMySQL-1.0.8.tar.gz", hash = "sha256:9edf59b23f4e3201a28274f86bb6e3588e1777d56617a07da4196df2d2dd2162"}, + {file = "types_PyMySQL-1.0.8-py3-none-any.whl", hash = "sha256:d26748b5a71ddd5576d2cdab4eac069614b9211f63b74c488ad7b0a7f936e737"}, ] types-requests = [ - {file = "types-requests-2.26.1.tar.gz", hash = "sha256:0893e112e1510bbb67f537941c92192de7472e51bf7f236e0e583866f0ed933e"}, - {file = "types_requests-2.26.1-py3-none-any.whl", hash = "sha256:853571b3accc188976c0f4feffcaebf6cdfc170082b5e43f3358aa78de61f531"}, + {file = "types-requests-2.26.3.tar.gz", hash = "sha256:d63fa617846dcefff5aa2d59e47ab4ffd806e4bb0567115f7adbb5e438302fe4"}, + {file = "types_requests-2.26.3-py3-none-any.whl", hash = "sha256:ad18284931c5ddbf050ccdd138f200d18fd56f88aa3567019d8da9b2d4fe0344"}, ] types-toml = [ {file = "types-toml-0.10.1.tar.gz", hash = "sha256:5c1f8f8d57692397c8f902bf6b4d913a0952235db7db17d2908cc110e70610cb"}, @@ -2507,8 +2541,8 @@ urllib3 = [ {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] virtualenv = [ - {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, - {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, + {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, + {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, ] watchdog = [ {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, diff --git a/pyproject.toml b/pyproject.toml index 9cb6509..9e8cd6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,8 +43,8 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6.2" databases = ">=0.3.2,<0.5.4" -pydantic = ">=1.6.1,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<=1.8.2" -SQLAlchemy = ">=1.3.18,<1.4.29" +pydantic = ">=1.6.1,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<=1.9.1" +SQLAlchemy = ">=1.3.18,<=1.4.29" asyncpg = { version = ">=0.24,<0.26", optional = true } psycopg2-binary = { version = "^2.9.1", optional = true } aiomysql = { version = ">=0.0.21,<0.0.23", optional = true } diff --git a/tests/test_model_definition/test_dates_with_timezone.py b/tests/test_model_definition/test_dates_with_timezone.py index 617276f..58b1136 100644 --- a/tests/test_model_definition/test_dates_with_timezone.py +++ b/tests/test_model_definition/test_dates_with_timezone.py @@ -55,6 +55,16 @@ class DateModel(ormar.Model): creation_date: date = ormar.Date() +class MyModel(ormar.Model): + id: int = ormar.Integer(primary_key=True) + created_at: datetime = ormar.DateTime(timezone=True, nullable=False) + + class Meta: + tablename = "mymodels" + metadata = metadata + database = database + + @pytest.fixture(autouse=True, scope="module") def create_test_database(): engine = sqlalchemy.create_engine(DATABASE_URL) @@ -116,3 +126,17 @@ async def test_query_with_time_in_filter(): assert len(outdated_samples) == 2 assert outdated_samples[0] == sample2 assert outdated_samples[1] == sample3 + + +@pytest.mark.asyncio +async def test_filtering_by_timezone_with_timedelta(): + now_utc = datetime.now(timezone.utc) + object = MyModel(created_at=now_utc) + await object.save() + + one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1) + created_since_one_hour_ago = await MyModel.objects.filter( + created_at__gte=one_hour_ago + ).all() + + assert len(created_since_one_hour_ago) == 1 From 3f264d974b0d47e3e99a792461991c0d79a0299b Mon Sep 17 00:00:00 2001 From: collerek Date: Wed, 5 Jan 2022 18:19:14 +0100 Subject: [PATCH 02/10] wip - remove literal binds --- ormar/queryset/actions/filter_action.py | 67 ++++++++++++++++++------- ormar/queryset/clause.py | 26 ++++++---- ormar/queryset/query.py | 1 + ormar/relations/alias_manager.py | 16 ++++-- 4 files changed, 77 insertions(+), 33 deletions(-) diff --git a/ormar/queryset/actions/filter_action.py b/ormar/queryset/actions/filter_action.py index 443b36f..dd20ec7 100644 --- a/ormar/queryset/actions/filter_action.py +++ b/ormar/queryset/actions/filter_action.py @@ -1,7 +1,7 @@ +import datetime from typing import Any, Dict, TYPE_CHECKING, Type import sqlalchemy -from sqlalchemy import text import ormar # noqa: I100, I202 from ormar.exceptions import QueryDefinitionError @@ -125,7 +125,7 @@ class FilterAction(QueryAction): sufix = "%" if "end" not in self.operator else "" self.filter_value = f"{prefix}{self.filter_value}{sufix}" - def get_text_clause(self) -> sqlalchemy.sql.expression.TextClause: + def get_text_clause(self) -> sqlalchemy.sql.expression.BinaryExpression: """ Escapes characters if it's required. Substitutes values of the models if value is a ormar Model with its pk value. @@ -137,18 +137,44 @@ class FilterAction(QueryAction): if isinstance(self.filter_value, ormar.Model): self.filter_value = self.filter_value.pk + # self._convert_dates_if_required() + op_attr = FILTER_OPERATORS[self.operator] if self.operator == "isnull": op_attr = "is_" if self.filter_value else "isnot" filter_value = None else: filter_value = self.filter_value - clause = getattr(self.column, op_attr)(filter_value) + if self.table_prefix: + aliased_table = self.source_model.Meta.alias_manager.prefixed_table_name( + self.table_prefix, self.column.table + ) + aliased_column = getattr(aliased_table.c, self.column.name) + else: + aliased_column = self.column + clause = getattr(aliased_column, op_attr)(filter_value) clause = self._compile_clause( clause, modifiers={"escape": "\\" if self.has_escaped_character else None} ) return clause + def _convert_dates_if_required(self) -> None: + """ + Converts dates, time and datetime to isoformat + """ + if isinstance( + self.filter_value, (datetime.date, datetime.time, datetime.datetime) + ): + self.filter_value = self.filter_value.isoformat() + + if isinstance(self.filter_value, (list, tuple, set)): + self.filter_value = [ + x.isoformat() + if isinstance(x, (datetime.date, datetime.time, datetime.datetime)) + else x + for x in self.filter_value + ] + def _compile_clause( self, clause: sqlalchemy.sql.expression.BinaryExpression, modifiers: Dict ) -> sqlalchemy.sql.expression.TextClause: @@ -166,19 +192,24 @@ class FilterAction(QueryAction): for modifier, modifier_value in modifiers.items(): clause.modifiers[modifier] = modifier_value - clause_text = str( - clause.compile( - dialect=self.target_model.Meta.database._backend._dialect, - compile_kwargs={"literal_binds": True}, - ) - ) - alias = f"{self.table_prefix}_" if self.table_prefix else "" - aliased_name = f"{alias}{self.table.name}.{self.column.name}" - clause_text = clause_text.replace( - f"{self.table.name}.{self.column.name}", aliased_name - ) - dialect_name = self.target_model.Meta.database._backend._dialect.name - if dialect_name != "sqlite": # pragma: no cover - clause_text = clause_text.replace("%%", "%") # remove %% in some dialects - clause = text(clause_text) + # compiled_clause = clause.compile( + # dialect=self.target_model.Meta.database._backend._dialect, + # # compile_kwargs={"literal_binds": True}, + # ) + # + # compiled_clause2 = clause.compile( + # dialect=self.target_model.Meta.database._backend._dialect, + # compile_kwargs={"literal_binds": True}, + # ) + # + # alias = f"{self.table_prefix}_" if self.table_prefix else "" + # aliased_name = f"{alias}{self.table.name}.{self.column.name}" + # clause_text = self.compile_query(compiled_query=compiled_clause) + # clause_text = clause_text.replace( + # f"{self.table.name}.{self.column.name}", aliased_name + # ) + # # dialect_name = self.target_model.Meta.database._backend._dialect.name + # # if dialect_name != "sqlite": # pragma: no cover + # # clause_text = clause_text.replace("%%", "%") + # clause = text(clause_text) return clause diff --git a/ormar/queryset/clause.py b/ormar/queryset/clause.py index 402dd6b..3ee6288 100644 --- a/ormar/queryset/clause.py +++ b/ormar/queryset/clause.py @@ -121,19 +121,23 @@ class FilterGroup: :return: complied and escaped clause :rtype: sqlalchemy.sql.elements.TextClause """ - prefix = " NOT " if self.exclude else "" + # prefix = " NOT " if self.exclude else "" if self.filter_type == FilterType.AND: - clause = sqlalchemy.text( - f"{prefix}( " - + str(sqlalchemy.sql.and_(*self._get_text_clauses())) - + " )" - ) + # clause = sqlalchemy.text( + # f"{prefix}( " + # + str(sqlalchemy.sql.and_(*self._get_text_clauses())) + # + " )" + # ) + clause = sqlalchemy.sql.and_(*self._get_text_clauses()) else: - clause = sqlalchemy.text( - f"{prefix}( " - + str(sqlalchemy.sql.or_(*self._get_text_clauses())) - + " )" - ) + # clause = sqlalchemy.text( + # f"{prefix}( " + # + str(sqlalchemy.sql.or_(*self._get_text_clauses())) + # + " )" + # ) + clause = sqlalchemy.sql.or_(*self._get_text_clauses()) + if self.exclude: + clause = sqlalchemy.sql.not_(clause) return clause diff --git a/ormar/queryset/query.py b/ormar/queryset/query.py index ddee8ad..bbfe012 100644 --- a/ormar/queryset/query.py +++ b/ormar/queryset/query.py @@ -187,6 +187,7 @@ class Query: for order in list(self.sorted_orders.keys()): if order is not None and order.get_field_name_text() != pk_aliased_name: aliased_col = order.get_field_name_text() + # maxes[aliased_col] = order.get_text_clause() maxes[aliased_col] = order.get_min_or_max() elif order.get_field_name_text() == pk_aliased_name: maxes[pk_aliased_name] = order.get_text_clause() diff --git a/ormar/relations/alias_manager.py b/ormar/relations/alias_manager.py index d13b7ea..e091e5e 100644 --- a/ormar/relations/alias_manager.py +++ b/ormar/relations/alias_manager.py @@ -35,6 +35,7 @@ class AliasManager: def __init__(self) -> None: self._aliases_new: Dict[str, str] = dict() self._reversed_aliases: Dict[str, str] = dict() + self._prefixed_tables: Dict[str, text] = dict() def __contains__(self, item: str) -> bool: return self._aliases_new.__contains__(item) @@ -77,15 +78,20 @@ class AliasManager: :rtype: List[text] """ alias = f"{alias}_" if alias else "" + aliased_fields = [f"{alias}{x}" for x in fields] + # TODO: check if normal fields still needed or only aliased one all_columns = ( table.columns if not fields - else [col for col in table.columns if col.name in fields] + else [ + col + for col in table.columns + if col.name in fields or col.name in aliased_fields + ] ) return [column.label(f"{alias}{column.name}") for column in all_columns] - @staticmethod - def prefixed_table_name(alias: str, table: sqlalchemy.Table) -> text: + def prefixed_table_name(self, alias: str, table: sqlalchemy.Table) -> text: """ Creates text clause with table name with aliased name. @@ -96,7 +102,9 @@ class AliasManager: :return: sqlalchemy text clause as "table_name aliased_name" :rtype: sqlalchemy text clause """ - return table.alias(f"{alias}_{table.name}") + full_alias = f"{alias}_{table.name}" + key = f"{full_alias}_{id(table)}" + return self._prefixed_tables.setdefault(key, table.alias(full_alias)) def add_relation_type( self, source_model: Type["Model"], relation_name: str, reverse_name: str = None From c8586e5b8e1d55db16f3d750423e24e23b349aa7 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 6 Jan 2022 18:22:07 +0100 Subject: [PATCH 03/10] rc for skip of literal binds --- .pre-commit-config.yaml | 3 + docs_src/fields/docs002.py | 4 +- docs_src/models/docs004.py | 4 +- docs_src/models/docs011.py | 3 +- docs_src/models/docs012.py | 2 +- docs_src/models/docs015.py | 2 +- docs_src/queries/docs002.py | 17 +++--- docs_src/queries/docs003.py | 19 +++--- docs_src/queries/docs005.py | 17 ++++-- docs_src/queries/docs006.py | 31 +++++++--- docs_src/queries/docs008.py | 60 ++++++++++++++----- docs_src/queries/docs009.py | 40 ++++++------- docs_src/relations/docs003.py | 2 +- ormar/queryset/actions/filter_action.py | 46 +------------- ormar/queryset/clause.py | 15 +---- ormar/relations/alias_manager.py | 2 +- pyproject.toml | 4 ++ .../test_fields_access.py | 30 ++++++---- tests/test_queries/test_filter_groups.py | 38 +++++++----- .../test_nested_reverse_relations.py | 4 ++ 20 files changed, 188 insertions(+), 155 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c57598f..1a1cc4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,15 +3,18 @@ repos: rev: 21.9b0 hooks: - id: black + exclude: docs_src - repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 + exclude: docs_src args: [ '--max-line-length=88' ] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910 hooks: - id: mypy + exclude: docs_src args: [--no-strict-optional, --ignore-missing-imports] additional_dependencies: [ types-ujson>=0.1.1, diff --git a/docs_src/fields/docs002.py b/docs_src/fields/docs002.py index 2432856..19df0d6 100644 --- a/docs_src/fields/docs002.py +++ b/docs_src/fields/docs002.py @@ -26,7 +26,9 @@ class Course(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) completed: bool = ormar.Boolean(default=False) - department: Optional[Department] = ormar.ForeignKey(Department, related_name="my_courses") + department: Optional[Department] = ormar.ForeignKey( + Department, related_name="my_courses" + ) department = Department(name="Science") diff --git a/docs_src/models/docs004.py b/docs_src/models/docs004.py index cc8bce8..4fe548a 100644 --- a/docs_src/models/docs004.py +++ b/docs_src/models/docs004.py @@ -8,7 +8,9 @@ metadata = sqlalchemy.MetaData() class Course(ormar.Model): - class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy + class Meta( + ormar.ModelMeta + ): # note you don't have to subclass - but it's recommended for ide completion and mypy database = database metadata = metadata diff --git a/docs_src/models/docs011.py b/docs_src/models/docs011.py index 962de1d..1f948ba 100644 --- a/docs_src/models/docs011.py +++ b/docs_src/models/docs011.py @@ -16,4 +16,5 @@ class Course(ormar.Model): name: ormar.String(max_length=100) completed: ormar.Boolean(default=False) -c1 = Course() \ No newline at end of file + +c1 = Course() diff --git a/docs_src/models/docs012.py b/docs_src/models/docs012.py index 68e75b9..2c74a77 100644 --- a/docs_src/models/docs012.py +++ b/docs_src/models/docs012.py @@ -14,4 +14,4 @@ class Course(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - completed = ormar.Boolean(default=False) \ No newline at end of file + completed = ormar.Boolean(default=False) diff --git a/docs_src/models/docs015.py b/docs_src/models/docs015.py index 828da0b..f3053b8 100644 --- a/docs_src/models/docs015.py +++ b/docs_src/models/docs015.py @@ -19,4 +19,4 @@ class Course(ormar.Model): @property_field def prefixed_name(self): - return 'custom_prefix__' + self.name + return "custom_prefix__" + self.name diff --git a/docs_src/queries/docs002.py b/docs_src/queries/docs002.py index d9cdeff..7d18aa7 100644 --- a/docs_src/queries/docs002.py +++ b/docs_src/queries/docs002.py @@ -15,14 +15,17 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', - choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + genre: str = ormar.String( + max_length=100, + default="Fiction", + choices=["Fiction", "Adventure", "Historic", "Fantasy"], + ) -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") +await Book.objects.create(title="War and Peace", author="Tolstoy, Leo", genre="Fiction") +await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") -await Book.objects.update(each=True, genre='Fiction') -all_books = await Book.objects.filter(genre='Fiction').all() +await Book.objects.update(each=True, genre="Fiction") +all_books = await Book.objects.filter(genre="Fiction").all() assert len(all_books) == 3 diff --git a/docs_src/queries/docs003.py b/docs_src/queries/docs003.py index 58e4f1b..d8b3b65 100644 --- a/docs_src/queries/docs003.py +++ b/docs_src/queries/docs003.py @@ -15,18 +15,23 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', - choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + genre: str = ormar.String( + max_length=100, + default="Fiction", + choices=["Fiction", "Adventure", "Historic", "Fantasy"], + ) -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") +await Book.objects.create(title="War and Peace", author="Tolstoy, Leo", genre="Fiction") +await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") # if not exist the instance will be persisted in db -vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction') +vol2 = await Book.objects.update_or_create( + title="Volume II", author="Anonymous", genre="Fiction" +) assert await Book.objects.count() == 1 # if pk or pkname passed in kwargs (like id here) the object will be updated -assert await Book.objects.update_or_create(id=vol2.id, genre='Historic') +assert await Book.objects.update_or_create(id=vol2.id, genre="Historic") assert await Book.objects.count() == 1 diff --git a/docs_src/queries/docs005.py b/docs_src/queries/docs005.py index dca0757..0111123 100644 --- a/docs_src/queries/docs005.py +++ b/docs_src/queries/docs005.py @@ -15,16 +15,21 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', - choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + genre: str = ormar.String( + max_length=100, + default="Fiction", + choices=["Fiction", "Adventure", "Historic", "Fantasy"], + ) -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") +await Book.objects.create( + title="War and Peace in Space", author="Tolstoy, Leo", genre="Fantasy" +) +await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") # delete accepts kwargs that will be used in filter # acting in same way as queryset.filter(**kwargs).delete() -await Book.objects.delete(genre='Fantasy') # delete all fantasy books +await Book.objects.delete(genre="Fantasy") # delete all fantasy books all_books = await Book.objects.all() assert len(all_books) == 2 diff --git a/docs_src/queries/docs006.py b/docs_src/queries/docs006.py index 13143d2..01a23f7 100644 --- a/docs_src/queries/docs006.py +++ b/docs_src/queries/docs006.py @@ -36,10 +36,27 @@ class Car(ormar.Model): # build some sample data toyota = await Company.objects.create(name="Toyota", founded=1937) -await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, - aircon_type='Auto') - +await Car.objects.create( + manufacturer=toyota, + name="Corolla", + year=2020, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", +) +await Car.objects.create( + manufacturer=toyota, + name="Yaris", + year=2019, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", +) +await Car.objects.create( + manufacturer=toyota, + name="Supreme", + year=2020, + gearbox_type="Auto", + gears=6, + aircon_type="Auto", +) diff --git a/docs_src/queries/docs008.py b/docs_src/queries/docs008.py index 2c79b61..e90912b 100644 --- a/docs_src/queries/docs008.py +++ b/docs_src/queries/docs008.py @@ -36,33 +36,65 @@ class Car(ormar.Model): # build some sample data toyota = await Company.objects.create(name="Toyota", founded=1937) -await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, - aircon_type='Auto') +await Car.objects.create( + manufacturer=toyota, + name="Corolla", + year=2020, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", +) +await Car.objects.create( + manufacturer=toyota, + name="Yaris", + year=2019, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", +) +await Car.objects.create( + manufacturer=toyota, + name="Supreme", + year=2020, + gearbox_type="Auto", + gears=6, + aircon_type="Auto", +) # select manufacturer but only name - to include related models use notation {model_name}__{column} -all_cars = await Car.objects.select_related('manufacturer').exclude_fields( - ['year', 'gearbox_type', 'gears', 'aircon_type', 'company__founded']).all() +all_cars = ( + await Car.objects.select_related("manufacturer") + .exclude_fields( + ["year", "gearbox_type", "gears", "aircon_type", "company__founded"] + ) + .all() +) for car in all_cars: # excluded columns will yield None - assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) + assert all( + getattr(car, x) is None + for x in ["year", "gearbox_type", "gears", "aircon_type"] + ) # included column on related models will be available, pk column is always included # even if you do not include it in fields list - assert car.manufacturer.name == 'Toyota' + assert car.manufacturer.name == "Toyota" # also in the nested related models - you cannot exclude pk - it's always auto added assert car.manufacturer.founded is None # fields() can be called several times, building up the columns to select # models selected in select_related but with no columns in fields list implies all fields -all_cars = await Car.objects.select_related('manufacturer').exclude_fields('year').exclude_fields( - ['gear', 'gearbox_type']).all() +all_cars = ( + await Car.objects.select_related("manufacturer") + .exclude_fields("year") + .exclude_fields(["gear", "gearbox_type"]) + .all() +) # all fiels from company model are selected -assert all_cars[0].manufacturer.name == 'Toyota' +assert all_cars[0].manufacturer.name == "Toyota" assert all_cars[0].manufacturer.founded == 1937 # cannot exclude mandatory model columns - company__name in this example - note usage of dict/set this time -await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all() +await Car.objects.select_related("manufacturer").exclude_fields( + [{"company": {"name"}}] +).all() # will raise pydantic ValidationError as company.name is required diff --git a/docs_src/queries/docs009.py b/docs_src/queries/docs009.py index 74ecc71..5e2bced 100644 --- a/docs_src/queries/docs009.py +++ b/docs_src/queries/docs009.py @@ -1,33 +1,29 @@ # 1. like in example above -await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all() +await Car.objects.select_related("manufacturer").fields( + ["id", "name", "manufacturer__name"] +).all() # 2. to mark a field as required use ellipsis -await Car.objects.select_related('manufacturer').fields({'id': ..., - 'name': ..., - 'manufacturer': { - 'name': ...} - }).all() +await Car.objects.select_related("manufacturer").fields( + {"id": ..., "name": ..., "manufacturer": {"name": ...}} +).all() # 3. to include whole nested model use ellipsis -await Car.objects.select_related('manufacturer').fields({'id': ..., - 'name': ..., - 'manufacturer': ... - }).all() +await Car.objects.select_related("manufacturer").fields( + {"id": ..., "name": ..., "manufacturer": ...} +).all() # 4. to specify fields at last nesting level you can also use set - equivalent to 2. above -await Car.objects.select_related('manufacturer').fields({'id': ..., - 'name': ..., - 'manufacturer': {'name'} - }).all() +await Car.objects.select_related("manufacturer").fields( + {"id": ..., "name": ..., "manufacturer": {"name"}} +).all() # 5. of course set can have multiple fields -await Car.objects.select_related('manufacturer').fields({'id': ..., - 'name': ..., - 'manufacturer': {'name', 'founded'} - }).all() +await Car.objects.select_related("manufacturer").fields( + {"id": ..., "name": ..., "manufacturer": {"name", "founded"}} +).all() # 6. you can include all nested fields but it will be equivalent of 3. above which is shorter -await Car.objects.select_related('manufacturer').fields({'id': ..., - 'name': ..., - 'manufacturer': {'id', 'name', 'founded'} - }).all() +await Car.objects.select_related("manufacturer").fields( + {"id": ..., "name": ..., "manufacturer": {"id", "name", "founded"}} +).all() diff --git a/docs_src/relations/docs003.py b/docs_src/relations/docs003.py index 03cc1ec..fe33608 100644 --- a/docs_src/relations/docs003.py +++ b/docs_src/relations/docs003.py @@ -14,4 +14,4 @@ class Course(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) - department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department) \ No newline at end of file + department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department) diff --git a/ormar/queryset/actions/filter_action.py b/ormar/queryset/actions/filter_action.py index dd20ec7..fcd7508 100644 --- a/ormar/queryset/actions/filter_action.py +++ b/ormar/queryset/actions/filter_action.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, TYPE_CHECKING, Type +from typing import Any, TYPE_CHECKING, Type import sqlalchemy @@ -153,9 +153,8 @@ class FilterAction(QueryAction): else: aliased_column = self.column clause = getattr(aliased_column, op_attr)(filter_value) - clause = self._compile_clause( - clause, modifiers={"escape": "\\" if self.has_escaped_character else None} - ) + if self.has_escaped_character: + clause.modifiers["escape"] = "\\" return clause def _convert_dates_if_required(self) -> None: @@ -174,42 +173,3 @@ class FilterAction(QueryAction): else x for x in self.filter_value ] - - def _compile_clause( - self, clause: sqlalchemy.sql.expression.BinaryExpression, modifiers: Dict - ) -> sqlalchemy.sql.expression.TextClause: - """ - Compiles the clause to str using appropriate database dialect, replace columns - names with aliased names and converts it back to TextClause. - - :param clause: original not compiled clause - :type clause: sqlalchemy.sql.elements.BinaryExpression - :param modifiers: sqlalchemy modifiers - used only to escape chars here - :type modifiers: Dict[str, NoneType] - :return: compiled and escaped clause - :rtype: sqlalchemy.sql.elements.TextClause - """ - for modifier, modifier_value in modifiers.items(): - clause.modifiers[modifier] = modifier_value - - # compiled_clause = clause.compile( - # dialect=self.target_model.Meta.database._backend._dialect, - # # compile_kwargs={"literal_binds": True}, - # ) - # - # compiled_clause2 = clause.compile( - # dialect=self.target_model.Meta.database._backend._dialect, - # compile_kwargs={"literal_binds": True}, - # ) - # - # alias = f"{self.table_prefix}_" if self.table_prefix else "" - # aliased_name = f"{alias}{self.table.name}.{self.column.name}" - # clause_text = self.compile_query(compiled_query=compiled_clause) - # clause_text = clause_text.replace( - # f"{self.table.name}.{self.column.name}", aliased_name - # ) - # # dialect_name = self.target_model.Meta.database._backend._dialect.name - # # if dialect_name != "sqlite": # pragma: no cover - # # clause_text = clause_text.replace("%%", "%") - # clause = text(clause_text) - return clause diff --git a/ormar/queryset/clause.py b/ormar/queryset/clause.py index 3ee6288..2a86d45 100644 --- a/ormar/queryset/clause.py +++ b/ormar/queryset/clause.py @@ -121,21 +121,10 @@ class FilterGroup: :return: complied and escaped clause :rtype: sqlalchemy.sql.elements.TextClause """ - # prefix = " NOT " if self.exclude else "" if self.filter_type == FilterType.AND: - # clause = sqlalchemy.text( - # f"{prefix}( " - # + str(sqlalchemy.sql.and_(*self._get_text_clauses())) - # + " )" - # ) - clause = sqlalchemy.sql.and_(*self._get_text_clauses()) + clause = sqlalchemy.sql.and_(*self._get_text_clauses()).self_group() else: - # clause = sqlalchemy.text( - # f"{prefix}( " - # + str(sqlalchemy.sql.or_(*self._get_text_clauses())) - # + " )" - # ) - clause = sqlalchemy.sql.or_(*self._get_text_clauses()) + clause = sqlalchemy.sql.or_(*self._get_text_clauses()).self_group() if self.exclude: clause = sqlalchemy.sql.not_(clause) return clause diff --git a/ormar/relations/alias_manager.py b/ormar/relations/alias_manager.py index e091e5e..4f004db 100644 --- a/ormar/relations/alias_manager.py +++ b/ormar/relations/alias_manager.py @@ -78,7 +78,7 @@ class AliasManager: :rtype: List[text] """ alias = f"{alias}_" if alias else "" - aliased_fields = [f"{alias}{x}" for x in fields] + aliased_fields = [f"{alias}{x}" for x in fields] if fields else [] # TODO: check if normal fields still needed or only aliased one all_columns = ( table.columns diff --git a/pyproject.toml b/pyproject.toml index 9e8cd6f..e45ebf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -152,6 +152,10 @@ disallow_untyped_calls = false disallow_untyped_defs = false disallow_incomplete_defs = false +[[tool.mypy.overrides]] +module = "docs_src.*" +ignore_errors = true + [[tool.mypy.overrides]] module = ["sqlalchemy.*", "asyncpg"] ignore_missing_imports = true diff --git a/tests/test_model_definition/test_fields_access.py b/tests/test_model_definition/test_fields_access.py index d1b0298..5140b3e 100644 --- a/tests/test_model_definition/test_fields_access.py +++ b/tests/test_model_definition/test_fields_access.py @@ -142,16 +142,16 @@ def test_combining_groups_together(): group = (Product.name == "Test") & (Product.rating >= 3.0) group.resolve(model_cls=Product) assert len(group._nested_groups) == 2 - assert str(group.get_text_clause()) == ( - "( ( product.name = 'Test' ) AND" " ( product.rating >= 3.0 ) )" - ) + assert str( + group.get_text_clause().compile(compile_kwargs={"literal_binds": True}) + ) == ("((product.name = 'Test') AND (product.rating >= 3.0))") group = ~((Product.name == "Test") & (Product.rating >= 3.0)) group.resolve(model_cls=Product) assert len(group._nested_groups) == 2 - assert str(group.get_text_clause()) == ( - " NOT ( ( product.name = 'Test' ) AND" " ( product.rating >= 3.0 ) )" - ) + assert str( + group.get_text_clause().compile(compile_kwargs={"literal_binds": True}) + ) == ("NOT ((product.name = 'Test') AND" " (product.rating >= 3.0))") group = ((Product.name == "Test") & (Product.rating >= 3.0)) | ( Product.category.name << (["Toys", "Books"]) @@ -159,11 +159,13 @@ def test_combining_groups_together(): group.resolve(model_cls=Product) assert len(group._nested_groups) == 2 assert len(group._nested_groups[0]._nested_groups) == 2 - group_str = str(group.get_text_clause()) + group_str = str( + group.get_text_clause().compile(compile_kwargs={"literal_binds": True}) + ) category_prefix = group._nested_groups[1].actions[0].table_prefix assert group_str == ( - "( ( ( product.name = 'Test' ) AND ( product.rating >= 3.0 ) ) " - f"OR ( {category_prefix}_categories.name IN ('Toys', 'Books') ) )" + "(((product.name = 'Test') AND (product.rating >= 3.0)) " + f"OR ({category_prefix}_categories.name IN ('Toys', 'Books')))" ) group = (Product.name % "Test") | ( @@ -173,15 +175,17 @@ def test_combining_groups_together(): group.resolve(model_cls=Product) assert len(group._nested_groups) == 2 assert len(group._nested_groups[1]._nested_groups) == 2 - group_str = str(group.get_text_clause()) + group_str = str( + group.get_text_clause().compile(compile_kwargs={"literal_binds": True}) + ) price_list_prefix = ( group._nested_groups[1]._nested_groups[0].actions[0].table_prefix ) category_prefix = group._nested_groups[1]._nested_groups[1].actions[0].table_prefix assert group_str == ( - f"( ( product.name LIKE '%Test%' ) " - f"OR ( ( {price_list_prefix}_price_lists.name LIKE 'Aa%' ) " - f"OR ( {category_prefix}_categories.name IN ('Toys', 'Books') ) ) )" + f"((product.name LIKE '%Test%') " + f"OR (({price_list_prefix}_price_lists.name LIKE 'Aa%') " + f"OR ({category_prefix}_categories.name IN ('Toys', 'Books'))))" ) diff --git a/tests/test_queries/test_filter_groups.py b/tests/test_queries/test_filter_groups.py index 21581b1..2e8a26b 100644 --- a/tests/test_queries/test_filter_groups.py +++ b/tests/test_queries/test_filter_groups.py @@ -40,9 +40,10 @@ def test_or_group(): assert result.actions[0].target_model == Author assert result.actions[1].target_model == Book assert ( - str(result.get_text_clause()) == f"( authors.name = 'aa' OR " + str(result.get_text_clause().compile(compile_kwargs={"literal_binds": True})) + == f"(authors.name = 'aa' OR " f"{result.actions[1].table_prefix}" - f"_books.title = 'bb' )" + f"_books.title = 'bb')" ) @@ -53,9 +54,10 @@ def test_and_group(): assert result.actions[0].target_model == Author assert result.actions[1].target_model == Book assert ( - str(result.get_text_clause()) == f"( authors.name = 'aa' AND " + str(result.get_text_clause().compile(compile_kwargs={"literal_binds": True})) + == f"(authors.name = 'aa' AND " f"{result.actions[1].table_prefix}" - f"_books.title = 'bb' )" + f"_books.title = 'bb')" ) @@ -68,12 +70,13 @@ def test_nested_and(): assert len(result._nested_groups) == 2 book_prefix = result._nested_groups[0].actions[1].table_prefix assert ( - str(result.get_text_clause()) == f"( ( authors.name = 'aa' OR " + str(result.get_text_clause().compile(compile_kwargs={"literal_binds": True})) + == f"((authors.name = 'aa' OR " f"{book_prefix}" - f"_books.title = 'bb' ) AND " - f"( authors.name = 'cc' OR " + f"_books.title = 'bb') AND " + f"(authors.name = 'cc' OR " f"{book_prefix}" - f"_books.title = 'dd' ) )" + f"_books.title = 'dd'))" ) @@ -84,11 +87,12 @@ def test_nested_group_and_action(): assert len(result._nested_groups) == 1 book_prefix = result._nested_groups[0].actions[1].table_prefix assert ( - str(result.get_text_clause()) == f"( ( authors.name = 'aa' OR " + str(result.get_text_clause().compile(compile_kwargs={"literal_binds": True})) + == f"((authors.name = 'aa' OR " f"{book_prefix}" - f"_books.title = 'bb' ) AND " + f"_books.title = 'bb') AND " f"{book_prefix}" - f"_books.title = 'dd' )" + f"_books.title = 'dd')" ) @@ -108,12 +112,14 @@ def test_deeply_nested_or(): assert len(result._nested_groups) == 2 assert len(result._nested_groups[0]._nested_groups) == 2 book_prefix = result._nested_groups[0]._nested_groups[0].actions[1].table_prefix - result_qry = str(result.get_text_clause()) + result_qry = str( + result.get_text_clause().compile(compile_kwargs={"literal_binds": True}) + ) expected_qry = ( - f"( ( ( authors.name = 'aa' OR {book_prefix}_books.title = 'bb' ) AND " - f"( authors.name = 'cc' OR {book_prefix}_books.title = 'dd' ) ) " - f"OR ( ( {book_prefix}_books.year < 1900 OR {book_prefix}_books.title = '11' ) AND " - f"( {book_prefix}_books.year > 'xx' OR {book_prefix}_books.title = '22' ) ) )" + f"(((authors.name = 'aa' OR {book_prefix}_books.title = 'bb') AND " + f"(authors.name = 'cc' OR {book_prefix}_books.title = 'dd')) " + f"OR (({book_prefix}_books.year < 1900 OR {book_prefix}_books.title = '11') AND" + f" ({book_prefix}_books.year > 'xx' OR {book_prefix}_books.title = '22')))" ) assert result_qry.replace("\n", "") == expected_qry.replace("\n", "") diff --git a/tests/test_queries/test_nested_reverse_relations.py b/tests/test_queries/test_nested_reverse_relations.py index f18f711..bbe2091 100644 --- a/tests/test_queries/test_nested_reverse_relations.py +++ b/tests/test_queries/test_nested_reverse_relations.py @@ -56,6 +56,10 @@ def create_test_database(): metadata.drop_all(engine) +@pytest.mark.skipif( + database._backend._dialect.name == "sqlite", + reason="wait for fix for sqlite in encode/databases", +) @pytest.mark.asyncio async def test_double_nested_reverse_relation(): async with database: From f91dfd3d61bba9339ecad32ceee2987a35b730a7 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 12 Nov 2020 11:08:27 +0100 Subject: [PATCH 04/10] rc for skip of literal binds --- docs_src/queries/docs009.py | 40 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs_src/queries/docs009.py b/docs_src/queries/docs009.py index 5e2bced..74ecc71 100644 --- a/docs_src/queries/docs009.py +++ b/docs_src/queries/docs009.py @@ -1,29 +1,33 @@ # 1. like in example above -await Car.objects.select_related("manufacturer").fields( - ["id", "name", "manufacturer__name"] -).all() +await Car.objects.select_related('manufacturer').fields(['id', 'name', 'manufacturer__name']).all() # 2. to mark a field as required use ellipsis -await Car.objects.select_related("manufacturer").fields( - {"id": ..., "name": ..., "manufacturer": {"name": ...}} -).all() +await Car.objects.select_related('manufacturer').fields({'id': ..., + 'name': ..., + 'manufacturer': { + 'name': ...} + }).all() # 3. to include whole nested model use ellipsis -await Car.objects.select_related("manufacturer").fields( - {"id": ..., "name": ..., "manufacturer": ...} -).all() +await Car.objects.select_related('manufacturer').fields({'id': ..., + 'name': ..., + 'manufacturer': ... + }).all() # 4. to specify fields at last nesting level you can also use set - equivalent to 2. above -await Car.objects.select_related("manufacturer").fields( - {"id": ..., "name": ..., "manufacturer": {"name"}} -).all() +await Car.objects.select_related('manufacturer').fields({'id': ..., + 'name': ..., + 'manufacturer': {'name'} + }).all() # 5. of course set can have multiple fields -await Car.objects.select_related("manufacturer").fields( - {"id": ..., "name": ..., "manufacturer": {"name", "founded"}} -).all() +await Car.objects.select_related('manufacturer').fields({'id': ..., + 'name': ..., + 'manufacturer': {'name', 'founded'} + }).all() # 6. you can include all nested fields but it will be equivalent of 3. above which is shorter -await Car.objects.select_related("manufacturer").fields( - {"id": ..., "name": ..., "manufacturer": {"id", "name", "founded"}} -).all() +await Car.objects.select_related('manufacturer').fields({'id': ..., + 'name': ..., + 'manufacturer': {'id', 'name', 'founded'} + }).all() From 646a901af5427fb09bb8f19e827dd134cb4203b7 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 6 Jan 2022 19:35:13 +0100 Subject: [PATCH 05/10] clean unused code --- .pre-commit-config.yaml | 2 +- ormar/queryset/actions/filter_action.py | 20 ------------------- ormar/relations/alias_manager.py | 1 - .../test_dates_with_timezone.py | 17 ++++++++-------- .../test_nested_reverse_relations.py | 4 ++-- 5 files changed, 12 insertions(+), 32 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a1cc4f..d720888 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 21.12b0 hooks: - id: black exclude: docs_src diff --git a/ormar/queryset/actions/filter_action.py b/ormar/queryset/actions/filter_action.py index fcd7508..e393333 100644 --- a/ormar/queryset/actions/filter_action.py +++ b/ormar/queryset/actions/filter_action.py @@ -1,4 +1,3 @@ -import datetime from typing import Any, TYPE_CHECKING, Type import sqlalchemy @@ -137,8 +136,6 @@ class FilterAction(QueryAction): if isinstance(self.filter_value, ormar.Model): self.filter_value = self.filter_value.pk - # self._convert_dates_if_required() - op_attr = FILTER_OPERATORS[self.operator] if self.operator == "isnull": op_attr = "is_" if self.filter_value else "isnot" @@ -156,20 +153,3 @@ class FilterAction(QueryAction): if self.has_escaped_character: clause.modifiers["escape"] = "\\" return clause - - def _convert_dates_if_required(self) -> None: - """ - Converts dates, time and datetime to isoformat - """ - if isinstance( - self.filter_value, (datetime.date, datetime.time, datetime.datetime) - ): - self.filter_value = self.filter_value.isoformat() - - if isinstance(self.filter_value, (list, tuple, set)): - self.filter_value = [ - x.isoformat() - if isinstance(x, (datetime.date, datetime.time, datetime.datetime)) - else x - for x in self.filter_value - ] diff --git a/ormar/relations/alias_manager.py b/ormar/relations/alias_manager.py index 4f004db..c8bd1a6 100644 --- a/ormar/relations/alias_manager.py +++ b/ormar/relations/alias_manager.py @@ -79,7 +79,6 @@ class AliasManager: """ alias = f"{alias}_" if alias else "" aliased_fields = [f"{alias}{x}" for x in fields] if fields else [] - # TODO: check if normal fields still needed or only aliased one all_columns = ( table.columns if not fields diff --git a/tests/test_model_definition/test_dates_with_timezone.py b/tests/test_model_definition/test_dates_with_timezone.py index 58b1136..afb6fbd 100644 --- a/tests/test_model_definition/test_dates_with_timezone.py +++ b/tests/test_model_definition/test_dates_with_timezone.py @@ -130,13 +130,14 @@ async def test_query_with_time_in_filter(): @pytest.mark.asyncio async def test_filtering_by_timezone_with_timedelta(): - now_utc = datetime.now(timezone.utc) - object = MyModel(created_at=now_utc) - await object.save() + async with database: + now_utc = datetime.now(timezone.utc) + object = MyModel(created_at=now_utc) + await object.save() - one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1) - created_since_one_hour_ago = await MyModel.objects.filter( - created_at__gte=one_hour_ago - ).all() + one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1) + created_since_one_hour_ago = await MyModel.objects.filter( + created_at__gte=one_hour_ago + ).all() - assert len(created_since_one_hour_ago) == 1 + assert len(created_since_one_hour_ago) == 1 diff --git a/tests/test_queries/test_nested_reverse_relations.py b/tests/test_queries/test_nested_reverse_relations.py index bbe2091..2f28362 100644 --- a/tests/test_queries/test_nested_reverse_relations.py +++ b/tests/test_queries/test_nested_reverse_relations.py @@ -48,7 +48,7 @@ class DataSourceTableColumn(ormar.Model): @pytest.fixture(autouse=True, scope="module") -def create_test_database(): +def create_test_database(): # pragma: no cover engine = sqlalchemy.create_engine(DATABASE_URL) metadata.drop_all(engine) metadata.create_all(engine) @@ -61,7 +61,7 @@ def create_test_database(): reason="wait for fix for sqlite in encode/databases", ) @pytest.mark.asyncio -async def test_double_nested_reverse_relation(): +async def test_double_nested_reverse_relation(): # pragma: no cover async with database: data_source = await DataSource(name="local").save() test_tables = [ From e7174e321c317f70cff879a7b73856ff09b35daf Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 6 Jan 2022 19:57:09 +0100 Subject: [PATCH 06/10] bump version, update release info --- docs/index.md | 8 +++++++- docs/releases.md | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 7621fc1..9ecee7d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -61,6 +61,12 @@ As of now `ormar` is supported by: * [`fastapi-crudrouter`](https://github.com/awtkns/fastapi-crudrouter) * [`fastapi-pagination`](https://github.com/uriyyo/fastapi-pagination) +Ormar remains sql dialect agnostic - so only columns working in all supported backends are implemented. + +It's relatively easy to implement columns for specific dialects as an extensions of ormar. + +Postgres specific columns implementation: [`ormar-postgres-extensions`](https://github.com/tophat/ormar-postgres-extensions) + If you maintain or use a different library and would like it to support `ormar` let us know how we can help. ### Dependencies @@ -74,7 +80,7 @@ Ormar is built with: ### License -`ormar` is built as open-sorce software and will remain completely free (MIT license). +`ormar` is built as open-source software and will remain completely free (MIT license). As I write open-source code to solve everyday problems in my work or to promote and build strong python community you can say thank you and buy me a coffee or sponsor me with a monthly amount to help ensure my work remains free and maintained. diff --git a/docs/releases.md b/docs/releases.md index f85243c..62d0926 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,3 +1,11 @@ +# 0.10.24 + +## 🐛 Fixes + +* Fix support for `pydantic==1.9.0` [#502](https://github.com/collerek/ormar/issues/502) +* Fix timezone issues with datetime [#504](https://github.com/collerek/ormar/issues/504) +* Remove literal binds in query generation to unblock postgres arrays [#/tophat/ormar-postgres-extensions/9](https://github.com/tophat/ormar-postgres-extensions/pull/9) + # 0.10.23 ## ✨ Features diff --git a/pyproject.toml b/pyproject.toml index e45ebf8..2f8e195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "ormar" [tool.poetry] name = "ormar" -version = "0.10.23" +version = "0.10.24" description = "A simple async ORM with fastapi in mind and pydantic validation." authors = ["Radosław Drążkiewicz "] license = "MIT" From bf3b36194b2b9ffb54643de2152f091f5ff465e7 Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 14 Jan 2022 16:54:26 +0100 Subject: [PATCH 07/10] bump databases, remove skip on sqlite test --- docs/releases.md | 5 + poetry.lock | 209 ++++++++++-------- pyproject.toml | 2 +- .../test_nested_reverse_relations.py | 6 +- 4 files changed, 120 insertions(+), 102 deletions(-) diff --git a/docs/releases.md b/docs/releases.md index 62d0926..f858a08 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -6,6 +6,11 @@ * Fix timezone issues with datetime [#504](https://github.com/collerek/ormar/issues/504) * Remove literal binds in query generation to unblock postgres arrays [#/tophat/ormar-postgres-extensions/9](https://github.com/tophat/ormar-postgres-extensions/pull/9) +## 💬 Other + +* Bump min. required `databases` version to `>=5.4`. + + # 0.10.23 ## ✨ Features diff --git a/poetry.lock b/poetry.lock index 77312ff..4150d5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -51,7 +51,7 @@ typing_extensions = ">=3.7.2" [[package]] name = "anyio" -version = "3.4.0" +version = "3.5.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "dev" optional = false @@ -65,7 +65,7 @@ sniffio = ">=1.1" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] trio = ["trio (>=0.16)"] @@ -200,7 +200,7 @@ python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" -version = "2.0.9" +version = "2.0.10" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false @@ -303,7 +303,7 @@ test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pr [[package]] name = "databases" -version = "0.5.3" +version = "0.5.4" description = "Async database support for Python." category = "main" optional = false @@ -315,13 +315,14 @@ sqlalchemy = ">=1.4,<1.5" [package.extras] mysql = ["aiomysql"] +mysql_asyncmy = ["asyncmy"] postgresql = ["asyncpg"] postgresql_aiopg = ["aiopg"] sqlite = ["aiosqlite"] [[package]] name = "databind.core" -version = "1.2.6" +version = "1.3.2" description = "Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses. Compatible with Python 3.7 and newer." category = "dev" optional = false @@ -341,14 +342,14 @@ test = ["types-dataclasses", "types-deprecated", "types-pkg-resources"] [[package]] name = "databind.json" -version = "1.2.6" +version = "1.3.2" description = "De-/serialize Python dataclasses to or from JSON payloads. Compatible with Python 3.7 and newer." category = "dev" optional = false python-versions = ">=3.7.0,<4.0.0" [package.dependencies] -"databind.core" = ">=1.2.6,<2.0.0" +"databind.core" = ">=1.3.2,<2.0.0" "nr.optional" = ">=0.1.1,<1.0.0" "nr.parsing.date" = ">=1.0.1,<2.0.0" "nr.preconditions" = ">=0.0.4,<1.0.0" @@ -640,7 +641,7 @@ docs = ["sphinx"] [[package]] name = "identify" -version = "2.4.1" +version = "2.4.4" description = "File identification library for Python" category = "dev" optional = false @@ -788,7 +789,7 @@ i18n = ["babel (>=2.9.0)"] [[package]] name = "mkdocs-material" -version = "8.1.4" +version = "8.1.6" description = "A Material Design theme for MkDocs" category = "dev" optional = false @@ -1097,7 +1098,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.1" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -1219,7 +1220,7 @@ pyyaml = "*" [[package]] name = "requests" -version = "2.27.0" +version = "2.27.1" description = "Python HTTP for Humans." category = "dev" optional = false @@ -1264,7 +1265,7 @@ contextvars = {version = ">=2.1", markers = "python_version < \"3.7\""} [[package]] name = "sqlalchemy" -version = "1.4.28" +version = "1.4.29" description = "Database Abstraction Library" category = "main" optional = false @@ -1368,7 +1369,7 @@ python-versions = "*" [[package]] name = "types-cryptography" -version = "3.3.10" +version = "3.3.12" description = "Typing stubs for cryptography" category = "dev" optional = false @@ -1380,7 +1381,7 @@ types-ipaddress = "*" [[package]] name = "types-dataclasses" -version = "0.6.2" +version = "0.6.4" description = "Typing stubs for dataclasses" category = "dev" optional = false @@ -1388,7 +1389,7 @@ python-versions = "*" [[package]] name = "types-enum34" -version = "1.1.1" +version = "1.1.2" description = "Typing stubs for enum34" category = "dev" optional = false @@ -1396,7 +1397,7 @@ python-versions = "*" [[package]] name = "types-ipaddress" -version = "1.0.1" +version = "1.0.2" description = "Typing stubs for ipaddress" category = "dev" optional = false @@ -1404,7 +1405,7 @@ python-versions = "*" [[package]] name = "types-orjson" -version = "3.6.1" +version = "3.6.2" description = "Typing stubs for orjson" category = "dev" optional = false @@ -1420,7 +1421,7 @@ python-versions = "*" [[package]] name = "types-pymysql" -version = "1.0.8" +version = "1.0.11" description = "Typing stubs for PyMySQL" category = "dev" optional = false @@ -1428,15 +1429,18 @@ python-versions = "*" [[package]] name = "types-requests" -version = "2.26.3" +version = "2.27.7" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" +[package.dependencies] +types-urllib3 = "<1.27" + [[package]] name = "types-toml" -version = "0.10.1" +version = "0.10.3" description = "Typing stubs for toml" category = "dev" optional = false @@ -1444,12 +1448,20 @@ python-versions = "*" [[package]] name = "types-ujson" -version = "4.2.0" +version = "4.2.1" description = "Typing stubs for ujson" category = "dev" optional = false python-versions = "*" +[[package]] +name = "types-urllib3" +version = "1.26.7" +description = "Typing stubs for urllib3" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "3.10.0.2" @@ -1460,7 +1472,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false @@ -1545,7 +1557,7 @@ sqlite = [] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "1b262c7c15f662e22a5f14061226a73ea82e7923e9d648c1a950119ac6216ce4" +content-hash = "e225b046ea8842875754f54a879c18d9e9ffb724758f5be9db2bbe88e3f59967" [metadata.files] aiocontextvars = [ @@ -1565,8 +1577,8 @@ aiosqlite = [ {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, ] anyio = [ - {file = "anyio-3.4.0-py3-none-any.whl", hash = "sha256:2855a9423524abcdd652d942f8932fda1735210f77a6b392eafd9ff34d3fe020"}, - {file = "anyio-3.4.0.tar.gz", hash = "sha256:24adc69309fb5779bc1e06158e143e0b6d2c56b302a3ac3de3083c705a6ed39d"}, + {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"}, + {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"}, ] astpretty = [ {file = "astpretty-2.1.0-py2.py3-none-any.whl", hash = "sha256:f81f14b5636f7af81fadb1e3c09ca7702ce4615500d9cc6d6829befb2dec2e3c"}, @@ -1681,8 +1693,8 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, - {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, + {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, + {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -1779,16 +1791,16 @@ cryptography = [ {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, ] databases = [ - {file = "databases-0.5.3-py3-none-any.whl", hash = "sha256:23862bd96241d8fcbf97eea82995ccb3baa8415c3cb106832b7509f296322f86"}, - {file = "databases-0.5.3.tar.gz", hash = "sha256:b69d74ee0b47fa30bb6e76db0c58da998e973393259d29215d8fb29352162bd6"}, + {file = "databases-0.5.4-py3-none-any.whl", hash = "sha256:85a6b0dd92e4bc95205c08141baf1e192c8aedb2159ce03bee39bb4117cfed83"}, + {file = "databases-0.5.4.tar.gz", hash = "sha256:04a3294d053bd8d9f4162fc4975ab11a3e9ad01ae37992adce84440725957fec"}, ] "databind.core" = [ - {file = "databind.core-1.2.6-py3-none-any.whl", hash = "sha256:aaf0b817cbcd8b0797485d2a762075146b64ee43287af42b9077ff9b7a981875"}, - {file = "databind.core-1.2.6.tar.gz", hash = "sha256:bd07c51bb19c772d731ab22998b0285454a9594cd803d3300900164751bd4bc0"}, + {file = "databind.core-1.3.2-py3-none-any.whl", hash = "sha256:e4cb849c730e651ddc6bd13e71066b7d87037251d7426366b231f6b7e51212c1"}, + {file = "databind.core-1.3.2.tar.gz", hash = "sha256:be1369191d9035a576e2724ec1246e5b76175a6d0ea62fcfbb3ecfe9f8bd5159"}, ] "databind.json" = [ - {file = "databind.json-1.2.6-py3-none-any.whl", hash = "sha256:9b729e0056135fbf97d382f589d6341bd5d3b6bb04d40c72db71f71c6c69cb52"}, - {file = "databind.json-1.2.6.tar.gz", hash = "sha256:50134de32c303b618ab67d251aa456f095101b2d8cddb347faf9eaeb1e984468"}, + {file = "databind.json-1.3.2-py3-none-any.whl", hash = "sha256:ee1c5aab957efa697dea069b9ccc837ab685f5c86afbc5657757b93a976c2dcd"}, + {file = "databind.json-1.3.2.tar.gz", hash = "sha256:3ac2253ddc0821067c2c75b169704e96150b26abbdf8b8894a3cb587f4a1f034"}, ] dataclasses = [ {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, @@ -1931,8 +1943,8 @@ greenlet = [ {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] identify = [ - {file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"}, - {file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"}, + {file = "identify-2.4.4-py2.py3-none-any.whl", hash = "sha256:aa68609c7454dbcaae60a01ff6b8df1de9b39fe6e50b1f6107ec81dcda624aa6"}, + {file = "identify-2.4.4.tar.gz", hash = "sha256:6b4b5031f69c48bf93a646b90de9b381c6b5f560df4cbe0ed3cf7650ae741e4d"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -2071,8 +2083,8 @@ mkdocs = [ {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, ] mkdocs-material = [ - {file = "mkdocs-material-8.1.4.tar.gz", hash = "sha256:b310be0f69503214406afe2e0495449f135503fe084467b607f860f4ed01f09e"}, - {file = "mkdocs_material-8.1.4-py2.py3-none-any.whl", hash = "sha256:6d615457ae18fd6e6a890a6f70d5681f3a6b931da67c2de7df1b273f8f702b4d"}, + {file = "mkdocs-material-8.1.6.tar.gz", hash = "sha256:12eb74faf018950f51261a773f9bea12cc296ec4bdbb2c8cf74102ee35b6df79"}, + {file = "mkdocs_material-8.1.6-py2.py3-none-any.whl", hash = "sha256:b2303413e3154502759f90ee2720b451be8855f769c385d8fb06a93ce54aafe2"}, ] mkdocs-material-extensions = [ {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, @@ -2316,8 +2328,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.11.1-py3-none-any.whl", hash = "sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c"}, - {file = "Pygments-2.11.1.tar.gz", hash = "sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pymdown-extensions = [ {file = "pymdown-extensions-9.1.tar.gz", hash = "sha256:74247f2c80f1d9e3c7242abe1c16317da36c6f26c7ad4b8a7f457f0ec20f0365"}, @@ -2383,8 +2395,8 @@ pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] requests = [ - {file = "requests-2.27.0-py2.py3-none-any.whl", hash = "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"}, - {file = "requests-2.27.0.tar.gz", hash = "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -2399,41 +2411,42 @@ sniffio = [ {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] sqlalchemy = [ - {file = "SQLAlchemy-1.4.28-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e659f256b7d402338563913bdeba53bf1eadd4c09e6f6dc93cc47938f7962a8f"}, - {file = "SQLAlchemy-1.4.28-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38df997ffa9007e953ad574f2263f61b9b683fd63ae397480ea4960be9bda0fd"}, - {file = "SQLAlchemy-1.4.28-cp27-cp27m-win_amd64.whl", hash = "sha256:6dd6fa51cf08d9433d28802228d2204e175324f1a284c4492e4af2dd36a2d485"}, - {file = "SQLAlchemy-1.4.28-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bb2d8530b7cc94b7fd9341843c3e49b6db48ea22313a8db9df21c41615b5e7b1"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:3b64f5d1c1d0e5f2ed4aa66f2b65ff6bdcdf4c5cc83b71c4bbf69695b09e9e19"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c02991e22ddce134ef1093ef5a9d5de448fc87b91432e4f879826e93cd1c7"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:387365c157e96eceacdd6c5468815ad05a523ba778680de4c8139a029e1fe044"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5639800f1cfe751569af2242041b30a08a6c0b9e5d95ed674ec8082d381eff13"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-win32.whl", hash = "sha256:261fcb3ff8c59e17ec44f9e61713a44ceaa97ae816da978d5cd1dc2c36f32478"}, - {file = "SQLAlchemy-1.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:29d10796e5604ab7bc067eda7231a2d2411a51eda43082673641245a49d1c4bb"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4490b10f83cd56ca2cdcd94b140d89911ac331e42a727b79157963b1b04fdd0c"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83ee7f6fa5faed23996c67044376d46815f65183ad6d744d94d68b18cdef060b"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f667a947378bcb12a371ab38bed1b708f3a682d1ba30176422652082919285a2"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61965abc63c8b54038574698888e91a126753a4bdc0ec001397acb14501834e0"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-win32.whl", hash = "sha256:41a02030f8934b0de843341e7014192a0c16ee2726a06da154c81153fbe56b33"}, - {file = "SQLAlchemy-1.4.28-cp36-cp36m-win_amd64.whl", hash = "sha256:c3497cd63c5f90112b8882ea4dd694052166f779ce9055cd5c4305e0b76d72d9"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5d91dce14ac3347bce301062ca825e7fb7e15c133f3909f15989e94878b1082f"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08e39d65b38d4c3f77c4c9bf090b0ba4ec5721a6e0a74b63d2a9781cdcacf142"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c85ead1d17acc5e8b282c578394dba253728bcbcbeb66e4ef0e25f4bab53935a"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daddcd6ba1706cc5fcc9cfaa913aa4bf331172dc7efd385fe3ee1feae3b513bc"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-win32.whl", hash = "sha256:ce4f2b34378561bc2e42635888fe86efe13d104ba1d95b5ca67b4d60d8e53e67"}, - {file = "SQLAlchemy-1.4.28-cp37-cp37m-win_amd64.whl", hash = "sha256:4999b03daa6c9afb9a0bf9e3b8769128ef1880557dacfca86fa7562920c49f6b"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:dd041324328cece3ccdf70cfbd71b5ab968e564a22318ffd88b054f5eadeb9be"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf2c1d64c4ee0f30e08e1844ff0acf3c1b6c4277c0e89ec3e8bf1722d245b108"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:525e962af8f25fc24ce019e6f237d49f8720d757a8a56c9b4caa2d91e2c66111"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72744fed32ecf2bf786d2e2f6756c04126c323ba939f47177b9722775626889"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-win32.whl", hash = "sha256:b5541355b8d4970753d4f7292f73a320704b20406e06cd29b469d156f0a484d8"}, - {file = "SQLAlchemy-1.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:cf3a3c2f32d53a4166b2eb8de35f93bcb640e51c32033024af500017d8e8a8c9"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:dfa093bd8ecfceafff62078910178567323005e44fbe4d7933e6cbce4512cea2"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555d56b71f61b4c9fa55fe203fe6e1e561c9385fa97c5849783ae050a89113af"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c90b21360cf14d33c8a004f991aa336c7906a8db825d4ec38722c5ff1c47dada"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2019b332cf4f9a513133fdf056dc4cecec7fbae7016ebc574d0f310103eed7ee"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-win32.whl", hash = "sha256:ca500f30619daf863ab1c66d57d53a0987361a8f3266454290198aabd18f2599"}, - {file = "SQLAlchemy-1.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:853de08e881dae0305647dd61b4429758f11d1bf02a9faf02793cad44bb2e0d5"}, - {file = "SQLAlchemy-1.4.28.tar.gz", hash = "sha256:7fdb7b775fb0739d3e71461509f978beb788935bc0aa9e47df14837cb33e5226"}, + {file = "SQLAlchemy-1.4.29-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:da64423c05256f4ab8c0058b90202053b201cbe3a081f3a43eb590cd554395ab"}, + {file = "SQLAlchemy-1.4.29-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0fc4eec2f46b40bdd42112b3be3fbbf88e194bcf02950fbb88bcdc1b32f07dc7"}, + {file = "SQLAlchemy-1.4.29-cp27-cp27m-win32.whl", hash = "sha256:101d2e100ba9182c9039699588e0b2d833c54b3bad46c67c192159876c9f27ea"}, + {file = "SQLAlchemy-1.4.29-cp27-cp27m-win_amd64.whl", hash = "sha256:ceac84dd9abbbe115e8be0c817bed85d9fa639b4d294e7817f9e61162d5f766c"}, + {file = "SQLAlchemy-1.4.29-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:15b65887b6c324cad638c7671cb95985817b733242a7eb69edd7cdf6953be1e0"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:78abc507d17753ed434b6cc0c0693126279723d5656d9775bfcac966a99a899b"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb8c993706e86178ce15a6b86a335a2064f52254b640e7f53365e716423d33f4"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:804e22d5b6165a4f3f019dd9c94bec5687de985a9c54286b93ded9f7846b8c82"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56d9d62021946263d4478c9ca012fbd1805f10994cb615c88e7bfd1ae14604d8"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-win32.whl", hash = "sha256:027f356c727db24f3c75828c7feb426f87ce1241242d08958e454bd025810660"}, + {file = "SQLAlchemy-1.4.29-cp310-cp310-win_amd64.whl", hash = "sha256:debaf09a823061f88a8dee04949814cf7e82fb394c5bca22c780cb03172ca23b"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:dc27dcc6c72eb38be7f144e9c2c4372d35a3684d3a6dd43bd98c1238358ee17c"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4ddd4f2e247128c58bb3dd4489922874afce157d2cff0b2295d67fcd0f22494"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9ce960a1dc60524136cf6f75621588e2508a117e04a6e3eedb0968bd13b8c824"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5919e647e1d4805867ea556ed4967c68b4d8b266059fa35020dbaed8ffdd60f3"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-win32.whl", hash = "sha256:886359f734b95ad1ef443b13bb4518bcade4db4f9553c9ce33d6d04ebda8d44e"}, + {file = "SQLAlchemy-1.4.29-cp36-cp36m-win_amd64.whl", hash = "sha256:e9cc6d844e24c307c3272677982a9b33816aeb45e4977791c3bdd47637a8d810"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5e9cd33459afa69c88fa648e803d1f1245e3caa60bfe8b80a9595e5edd3bda9c"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeaebceb24b46e884c4ad3c04f37feb178b81f6ce720af19bfa2592ca32fdef7"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e89347d3bd2ef873832b47e85f4bbd810a5e626c5e749d90a07638da100eb1c8"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a717c2e70fd1bb477161c4cc85258e41d978584fbe5522613618195f7e87d9b"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-win32.whl", hash = "sha256:f74d6c05d2d163464adbdfbc1ab85048cc15462ff7d134b8aed22bd521e1faa5"}, + {file = "SQLAlchemy-1.4.29-cp37-cp37m-win_amd64.whl", hash = "sha256:621854dbb4d2413c759a5571564170de45ef37299df52e78e62b42e2880192e1"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:f3909194751bb6cb7c5511dd18bcf77e6e3f0b31604ed4004dffa9461f71e737"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd49d21d1f03c81fbec9080ecdc4486d5ddda67e7fbb75ebf48294465c022cdc"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e5f6959466a42b6569774c257e55f9cd85200d5b0ba09f0f5d8b5845349c5822"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0072f9887aabe66db23f818bbe950cfa1b6127c5cb769b00bcc07935b3adb0ad"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-win32.whl", hash = "sha256:ad618d687d26d4cbfa9c6fa6141d59e05bcdfc60cb6e1f1d3baa18d8c62fef5f"}, + {file = "SQLAlchemy-1.4.29-cp38-cp38-win_amd64.whl", hash = "sha256:878daecb6405e786b07f97e1c77a9cfbbbec17432e8a90c487967e32cfdecb33"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:e027bdf0a4cf6bd0a3ad3b998643ea374d7991bd117b90bf9982e41ceb742941"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5de7adfb91d351f44062b8dedf29f49d4af7cb765be65816e79223a4e31062b"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fbc6e63e481fa323036f305ada96a3362e1d60dd2bfa026cac10c3553e6880e9"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dd0502cb091660ad0d89c5e95a29825f37cde2a5249957838e975871fbffaad"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-win32.whl", hash = "sha256:37b46bfc4af3dc226acb6fa28ecd2e1fd223433dc5e15a2bad62bf0a0cbb4e8b"}, + {file = "SQLAlchemy-1.4.29-cp39-cp39-win_amd64.whl", hash = "sha256:08cfd35eecaba79be930c9bfd2e1f0c67a7e1314355d83a378f9a512b1cf7587"}, + {file = "SQLAlchemy-1.4.29.tar.gz", hash = "sha256:fa2bad14e1474ba649cfc969c1d2ec915dd3e79677f346bbfe08e93ef9020b39"}, ] starlette = [ {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, @@ -2492,44 +2505,48 @@ types-aiofiles = [ {file = "types_aiofiles-0.7.3-py3-none-any.whl", hash = "sha256:fd7f410a94e0362c7a3cf7a98e62f972f1dab245130dfdf6f48914f462994262"}, ] types-cryptography = [ - {file = "types-cryptography-3.3.10.tar.gz", hash = "sha256:b5610287f57c68dabb9123bb76384bcacbf64d0d8282004a2434b84c7aaf9a0e"}, - {file = "types_cryptography-3.3.10-py3-none-any.whl", hash = "sha256:fac6e1cefb700f2c072b7bdcc52ee85b7f9e054a907ce0d56c8a3e2bb7bacbff"}, + {file = "types-cryptography-3.3.12.tar.gz", hash = "sha256:aab189d3a63453fba48a9e5937f354ed8d4d2151c0aef44dc813cdcce631f375"}, + {file = "types_cryptography-3.3.12-py3-none-any.whl", hash = "sha256:1a81e18c2456f996d10b2410d16fbdc6f19653131e4ce0e09978015e9207c476"}, ] types-dataclasses = [ - {file = "types-dataclasses-0.6.2.tar.gz", hash = "sha256:d74876d7a5e39ae6285765dd871e3b85d8186251f7b48e18130a35024c78bf73"}, - {file = "types_dataclasses-0.6.2-py3-none-any.whl", hash = "sha256:19ea746937b82bfa0d3bba1dd78858eca2ed81932eae888c3f2966aa3fd0aec6"}, + {file = "types-dataclasses-0.6.4.tar.gz", hash = "sha256:2f7ab6c565cf05cc7f27f31a4c2fcc803384e319aab292807b857ddf1473429f"}, + {file = "types_dataclasses-0.6.4-py3-none-any.whl", hash = "sha256:fef6ed4742ca27996530c6d549cd704772a4a86e4781841c9bb387001e369ec3"}, ] types-enum34 = [ - {file = "types-enum34-1.1.1.tar.gz", hash = "sha256:55c44c44f193636ed82f1cb68a9a632e1ea7096724f024c25e015976809df339"}, - {file = "types_enum34-1.1.1-py3-none-any.whl", hash = "sha256:b6d55d7c91867bd2fd6fc90651d91629c98b27cb26df11b1db658ae7a72bb768"}, + {file = "types-enum34-1.1.2.tar.gz", hash = "sha256:22a08eacf89a1b71b2b770321b6940abe12afd6214c12917c4f119c935ff732a"}, + {file = "types_enum34-1.1.2-py3-none-any.whl", hash = "sha256:a1e1dcb80ae9d5a86c69ac7fcd65aec529541faaedffb3b2c840b0cbed8fbd61"}, ] types-ipaddress = [ - {file = "types-ipaddress-1.0.1.tar.gz", hash = "sha256:dc5540c7fd8d4b3ffe8461bc01f27513c0abe5f2088e491218bd0a98a0b4584e"}, - {file = "types_ipaddress-1.0.1-py3-none-any.whl", hash = "sha256:9d0a642526c4a1f87ee9ae29d91b93f708508b891d038db2e1c9f06219383516"}, + {file = "types-ipaddress-1.0.2.tar.gz", hash = "sha256:b3f29a5e1dabab9ec00c75654b53b07251f731d57295097c72c864524a31034d"}, + {file = "types_ipaddress-1.0.2-py3-none-any.whl", hash = "sha256:cb5eb3ad21acea538a1b404bbe2c43a7ba918e56d94c7399730cfece01b0a947"}, ] types-orjson = [ - {file = "types-orjson-3.6.1.tar.gz", hash = "sha256:6bc2245b242c9ab4c8845495a4c16a51fa22510c8ac1d83da027ab3ce1f77d8a"}, - {file = "types_orjson-3.6.1-py3-none-any.whl", hash = "sha256:b3e991dd05fc6571a423996cbde37183123983783db64c5bc53e985ae32c7176"}, + {file = "types-orjson-3.6.2.tar.gz", hash = "sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638"}, + {file = "types_orjson-3.6.2-py3-none-any.whl", hash = "sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4"}, ] types-pkg-resources = [ {file = "types-pkg_resources-0.1.3.tar.gz", hash = "sha256:834a9b8d3dbea343562fd99d5d3359a726f6bf9d3733bccd2b4f3096fbab9dae"}, {file = "types_pkg_resources-0.1.3-py2.py3-none-any.whl", hash = "sha256:0cb9972cee992249f93fff1a491bf2dc3ce674e5a1926e27d4f0866f7d9b6d9c"}, ] types-pymysql = [ - {file = "types-PyMySQL-1.0.8.tar.gz", hash = "sha256:9edf59b23f4e3201a28274f86bb6e3588e1777d56617a07da4196df2d2dd2162"}, - {file = "types_PyMySQL-1.0.8-py3-none-any.whl", hash = "sha256:d26748b5a71ddd5576d2cdab4eac069614b9211f63b74c488ad7b0a7f936e737"}, + {file = "types-PyMySQL-1.0.11.tar.gz", hash = "sha256:85e77c19a742654b2a86828a9c78655a83985f2dbaa9330b3fdab6485da52cf6"}, + {file = "types_PyMySQL-1.0.11-py3-none-any.whl", hash = "sha256:0a1638b857a531241d102aa22113b365ad43f05c06c86bbde92e9504c53dc6ae"}, ] types-requests = [ - {file = "types-requests-2.26.3.tar.gz", hash = "sha256:d63fa617846dcefff5aa2d59e47ab4ffd806e4bb0567115f7adbb5e438302fe4"}, - {file = "types_requests-2.26.3-py3-none-any.whl", hash = "sha256:ad18284931c5ddbf050ccdd138f200d18fd56f88aa3567019d8da9b2d4fe0344"}, + {file = "types-requests-2.27.7.tar.gz", hash = "sha256:f38bd488528cdcbce5b01dc953972f3cead0d060cfd9ee35b363066c25bab13c"}, + {file = "types_requests-2.27.7-py3-none-any.whl", hash = "sha256:2e0e100dd489f83870d4f61949d3a7eae4821e7bfbf46c57e463c38f92d473d4"}, ] types-toml = [ - {file = "types-toml-0.10.1.tar.gz", hash = "sha256:5c1f8f8d57692397c8f902bf6b4d913a0952235db7db17d2908cc110e70610cb"}, - {file = "types_toml-0.10.1-py3-none-any.whl", hash = "sha256:8cdfd2b7c89bed703158b042dd5cf04255dae77096db66f4a12ca0a93ccb07a5"}, + {file = "types-toml-0.10.3.tar.gz", hash = "sha256:215a7a79198651ec5bdfd66193c1e71eb681a42f3ef7226c9af3123ced62564a"}, + {file = "types_toml-0.10.3-py3-none-any.whl", hash = "sha256:988457744d9774d194e3539388772e3a685d8057b7c4a89407afeb0a6cbd1b14"}, ] types-ujson = [ - {file = "types-ujson-4.2.0.tar.gz", hash = "sha256:b97ab027754c0788e7083eaa4a57d5837fb248d0e59879e80f26fc1ad6e58eea"}, - {file = "types_ujson-4.2.0-py3-none-any.whl", hash = "sha256:8b1cc4eeae31a3f9c01809011671e8bc41af5513022342baec6474436522d076"}, + {file = "types-ujson-4.2.1.tar.gz", hash = "sha256:9e7576316914151f4e7086fae4c5aea3a17ae2d66c2edea6525f4c82354c364d"}, + {file = "types_ujson-4.2.1-py3-none-any.whl", hash = "sha256:dd6ef2d1a29561e8fbbe4fa95e3509a1104c8f1d7d77004ae50ff99bfa89320c"}, +] +types-urllib3 = [ + {file = "types-urllib3-1.26.7.tar.gz", hash = "sha256:cfd1fbbe4ba9a605ed148294008aac8a7b8b7472651d1cc357d507ae5962e3d2"}, + {file = "types_urllib3-1.26.7-py3-none-any.whl", hash = "sha256:3adcf2cb5981809091dbff456e6999fe55f201652d8c360f99997de5ac2f556e"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, @@ -2537,8 +2554,8 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, diff --git a/pyproject.toml b/pyproject.toml index 2f8e195..927675d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6.2" -databases = ">=0.3.2,<0.5.4" +databases = ">=0.5.4,<0.5.5" pydantic = ">=1.6.1,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<=1.9.1" SQLAlchemy = ">=1.3.18,<=1.4.29" asyncpg = { version = ">=0.24,<0.26", optional = true } diff --git a/tests/test_queries/test_nested_reverse_relations.py b/tests/test_queries/test_nested_reverse_relations.py index 2f28362..914adc5 100644 --- a/tests/test_queries/test_nested_reverse_relations.py +++ b/tests/test_queries/test_nested_reverse_relations.py @@ -56,12 +56,8 @@ def create_test_database(): # pragma: no cover metadata.drop_all(engine) -@pytest.mark.skipif( - database._backend._dialect.name == "sqlite", - reason="wait for fix for sqlite in encode/databases", -) @pytest.mark.asyncio -async def test_double_nested_reverse_relation(): # pragma: no cover +async def test_double_nested_reverse_relation(): async with database: data_source = await DataSource(name="local").save() test_tables = [ From 53a26364215f0ae0227e8ee2851130976658b755 Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 14 Jan 2022 17:36:18 +0100 Subject: [PATCH 08/10] revert docs changes, pin databases, fix mypy --- .pre-commit-config.yaml | 6 ++-- docs_src/fields/docs002.py | 4 +-- docs_src/models/docs004.py | 4 +-- docs_src/models/docs011.py | 3 +- docs_src/queries/docs002.py | 17 +++++----- docs_src/queries/docs003.py | 19 +++++------- docs_src/queries/docs005.py | 17 ++++------ docs_src/queries/docs006.py | 31 +++++-------------- docs_src/queries/docs008.py | 60 +++++++++--------------------------- ormar/fields/model_fields.py | 2 -- ormar/queryset/queryset.py | 13 +++++++- poetry.lock | 2 +- pyproject.toml | 2 +- 13 files changed, 61 insertions(+), 119 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d720888..7c8f35d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,18 +3,18 @@ repos: rev: 21.12b0 hooks: - id: black - exclude: docs_src + exclude: ^(docs_src/|examples/) - repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 - exclude: docs_src + exclude: ^(docs_src/|examples/|tests/) args: [ '--max-line-length=88' ] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910 hooks: - id: mypy - exclude: docs_src + exclude: ^(docs_src/|examples/) args: [--no-strict-optional, --ignore-missing-imports] additional_dependencies: [ types-ujson>=0.1.1, diff --git a/docs_src/fields/docs002.py b/docs_src/fields/docs002.py index 19df0d6..2432856 100644 --- a/docs_src/fields/docs002.py +++ b/docs_src/fields/docs002.py @@ -26,9 +26,7 @@ class Course(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) completed: bool = ormar.Boolean(default=False) - department: Optional[Department] = ormar.ForeignKey( - Department, related_name="my_courses" - ) + department: Optional[Department] = ormar.ForeignKey(Department, related_name="my_courses") department = Department(name="Science") diff --git a/docs_src/models/docs004.py b/docs_src/models/docs004.py index 4fe548a..cc8bce8 100644 --- a/docs_src/models/docs004.py +++ b/docs_src/models/docs004.py @@ -8,9 +8,7 @@ metadata = sqlalchemy.MetaData() class Course(ormar.Model): - class Meta( - ormar.ModelMeta - ): # note you don't have to subclass - but it's recommended for ide completion and mypy + class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy database = database metadata = metadata diff --git a/docs_src/models/docs011.py b/docs_src/models/docs011.py index 1f948ba..962de1d 100644 --- a/docs_src/models/docs011.py +++ b/docs_src/models/docs011.py @@ -16,5 +16,4 @@ class Course(ormar.Model): name: ormar.String(max_length=100) completed: ormar.Boolean(default=False) - -c1 = Course() +c1 = Course() \ No newline at end of file diff --git a/docs_src/queries/docs002.py b/docs_src/queries/docs002.py index 7d18aa7..d9cdeff 100644 --- a/docs_src/queries/docs002.py +++ b/docs_src/queries/docs002.py @@ -15,17 +15,14 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String( - max_length=100, - default="Fiction", - choices=["Fiction", "Adventure", "Historic", "Fantasy"], - ) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) -await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") -await Book.objects.create(title="War and Peace", author="Tolstoy, Leo", genre="Fiction") -await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') -await Book.objects.update(each=True, genre="Fiction") -all_books = await Book.objects.filter(genre="Fiction").all() +await Book.objects.update(each=True, genre='Fiction') +all_books = await Book.objects.filter(genre='Fiction').all() assert len(all_books) == 3 diff --git a/docs_src/queries/docs003.py b/docs_src/queries/docs003.py index d8b3b65..58e4f1b 100644 --- a/docs_src/queries/docs003.py +++ b/docs_src/queries/docs003.py @@ -15,23 +15,18 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String( - max_length=100, - default="Fiction", - choices=["Fiction", "Adventure", "Historic", "Fantasy"], - ) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) -await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") -await Book.objects.create(title="War and Peace", author="Tolstoy, Leo", genre="Fiction") -await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') # if not exist the instance will be persisted in db -vol2 = await Book.objects.update_or_create( - title="Volume II", author="Anonymous", genre="Fiction" -) +vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction') assert await Book.objects.count() == 1 # if pk or pkname passed in kwargs (like id here) the object will be updated -assert await Book.objects.update_or_create(id=vol2.id, genre="Historic") +assert await Book.objects.update_or_create(id=vol2.id, genre='Historic') assert await Book.objects.count() == 1 diff --git a/docs_src/queries/docs005.py b/docs_src/queries/docs005.py index 0111123..dca0757 100644 --- a/docs_src/queries/docs005.py +++ b/docs_src/queries/docs005.py @@ -15,21 +15,16 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) author: str = ormar.String(max_length=100) - genre: str = ormar.String( - max_length=100, - default="Fiction", - choices=["Fiction", "Adventure", "Historic", "Fantasy"], - ) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) -await Book.objects.create(title="Tom Sawyer", author="Twain, Mark", genre="Adventure") -await Book.objects.create( - title="War and Peace in Space", author="Tolstoy, Leo", genre="Fantasy" -) -await Book.objects.create(title="Anna Karenina", author="Tolstoy, Leo", genre="Fiction") +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') # delete accepts kwargs that will be used in filter # acting in same way as queryset.filter(**kwargs).delete() -await Book.objects.delete(genre="Fantasy") # delete all fantasy books +await Book.objects.delete(genre='Fantasy') # delete all fantasy books all_books = await Book.objects.all() assert len(all_books) == 2 diff --git a/docs_src/queries/docs006.py b/docs_src/queries/docs006.py index 01a23f7..13143d2 100644 --- a/docs_src/queries/docs006.py +++ b/docs_src/queries/docs006.py @@ -36,27 +36,10 @@ class Car(ormar.Model): # build some sample data toyota = await Company.objects.create(name="Toyota", founded=1937) -await Car.objects.create( - manufacturer=toyota, - name="Corolla", - year=2020, - gearbox_type="Manual", - gears=5, - aircon_type="Manual", -) -await Car.objects.create( - manufacturer=toyota, - name="Yaris", - year=2019, - gearbox_type="Manual", - gears=5, - aircon_type="Manual", -) -await Car.objects.create( - manufacturer=toyota, - name="Supreme", - year=2020, - gearbox_type="Auto", - gears=6, - aircon_type="Auto", -) +await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, + aircon_type='Auto') + diff --git a/docs_src/queries/docs008.py b/docs_src/queries/docs008.py index e90912b..2c79b61 100644 --- a/docs_src/queries/docs008.py +++ b/docs_src/queries/docs008.py @@ -36,65 +36,33 @@ class Car(ormar.Model): # build some sample data toyota = await Company.objects.create(name="Toyota", founded=1937) -await Car.objects.create( - manufacturer=toyota, - name="Corolla", - year=2020, - gearbox_type="Manual", - gears=5, - aircon_type="Manual", -) -await Car.objects.create( - manufacturer=toyota, - name="Yaris", - year=2019, - gearbox_type="Manual", - gears=5, - aircon_type="Manual", -) -await Car.objects.create( - manufacturer=toyota, - name="Supreme", - year=2020, - gearbox_type="Auto", - gears=6, - aircon_type="Auto", -) +await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, + aircon_type='Auto') # select manufacturer but only name - to include related models use notation {model_name}__{column} -all_cars = ( - await Car.objects.select_related("manufacturer") - .exclude_fields( - ["year", "gearbox_type", "gears", "aircon_type", "company__founded"] - ) - .all() -) +all_cars = await Car.objects.select_related('manufacturer').exclude_fields( + ['year', 'gearbox_type', 'gears', 'aircon_type', 'company__founded']).all() for car in all_cars: # excluded columns will yield None - assert all( - getattr(car, x) is None - for x in ["year", "gearbox_type", "gears", "aircon_type"] - ) + assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) # included column on related models will be available, pk column is always included # even if you do not include it in fields list - assert car.manufacturer.name == "Toyota" + assert car.manufacturer.name == 'Toyota' # also in the nested related models - you cannot exclude pk - it's always auto added assert car.manufacturer.founded is None # fields() can be called several times, building up the columns to select # models selected in select_related but with no columns in fields list implies all fields -all_cars = ( - await Car.objects.select_related("manufacturer") - .exclude_fields("year") - .exclude_fields(["gear", "gearbox_type"]) - .all() -) +all_cars = await Car.objects.select_related('manufacturer').exclude_fields('year').exclude_fields( + ['gear', 'gearbox_type']).all() # all fiels from company model are selected -assert all_cars[0].manufacturer.name == "Toyota" +assert all_cars[0].manufacturer.name == 'Toyota' assert all_cars[0].manufacturer.founded == 1937 # cannot exclude mandatory model columns - company__name in this example - note usage of dict/set this time -await Car.objects.select_related("manufacturer").exclude_fields( - [{"company": {"name"}}] -).all() +await Car.objects.select_related('manufacturer').exclude_fields([{'company': {'name'}}]).all() # will raise pydantic ValidationError as company.name is required diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index a819b61..7a10c25 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -381,7 +381,6 @@ if TYPE_CHECKING: # pragma: nocover def Boolean(**kwargs: Any) -> bool: pass - else: class Boolean(ModelFieldFactory, int): @@ -545,7 +544,6 @@ if TYPE_CHECKING: # pragma: nocover # noqa: C901 ) -> Union[str, bytes]: pass - else: class LargeBinary(ModelFieldFactory, bytes): diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 7ecb6fa..0326a5d 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -18,6 +18,15 @@ import databases import sqlalchemy from sqlalchemy import bindparam +try: + from sqlalchemy.engine import LegacyRow +except ImportError: + if TYPE_CHECKING: + + class LegacyRow(dict): # type: ignore + pass + + import ormar # noqa I100 from ormar import MultipleMatches, NoMatch from ormar.exceptions import ModelPersistenceError, QueryDefinitionError @@ -605,7 +614,9 @@ class QuerySet(Generic[T]): model_cls=self.model_cls, # type: ignore exclude_through=exclude_through, ) - column_map = alias_resolver.resolve_columns(columns_names=list(rows[0].keys())) + column_map = alias_resolver.resolve_columns( + columns_names=list(cast(LegacyRow, rows[0]).keys()) + ) result = [ {column_map.get(k): v for k, v in dict(x).items() if k in column_map} for x in rows diff --git a/poetry.lock b/poetry.lock index 4150d5a..5bcdf7e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1557,7 +1557,7 @@ sqlite = [] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "e225b046ea8842875754f54a879c18d9e9ffb724758f5be9db2bbe88e3f59967" +content-hash = "7f70457628e806c602066d934eeac8f5550e53107b31906a5ec3a20bca9dbbda" [metadata.files] aiocontextvars = [ diff --git a/pyproject.toml b/pyproject.toml index 927675d..d5c40c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6.2" -databases = ">=0.5.4,<0.5.5" +databases = ">=0.3.2,!=0.5.0,!=0.5.1,!=0.5.2,!=0.5.3,<0.5.5" pydantic = ">=1.6.1,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<=1.9.1" SQLAlchemy = ">=1.3.18,<=1.4.29" asyncpg = { version = ">=0.24,<0.26", optional = true } From df0ac00dee59b14844f5ff572858ed672f7f9d7e Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 14 Jan 2022 17:54:13 +0100 Subject: [PATCH 09/10] fix coverage --- ormar/queryset/queryset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 0326a5d..1782567 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -20,7 +20,7 @@ from sqlalchemy import bindparam try: from sqlalchemy.engine import LegacyRow -except ImportError: +except ImportError: # pragma: no cover if TYPE_CHECKING: class LegacyRow(dict): # type: ignore From 5677bda05498c00285239af6f25d95c7450ef094 Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 14 Jan 2022 18:27:49 +0100 Subject: [PATCH 10/10] fix json fields in bulk operations --- docs/releases.md | 7 +++- ormar/decorators/signals.py | 4 +- ormar/exceptions.py | 1 + ormar/models/mixins/save_mixin.py | 32 ++++++++++++--- ormar/queryset/queryset.py | 17 ++++---- ormar/signals/signal.py | 4 +- .../test_queryset_level_methods.py | 39 +++++++++++++++++-- tests/test_signals/test_signals.py | 13 +++++-- 8 files changed, 90 insertions(+), 27 deletions(-) diff --git a/docs/releases.md b/docs/releases.md index f858a08..a5576a2 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,16 +1,21 @@ # 0.10.24 +## ✨ Features + +* Add `post_bulk_update` signal (by @ponytailer - thanks!) [#524](https://github.com/collerek/ormar/pull/524) + ## 🐛 Fixes * Fix support for `pydantic==1.9.0` [#502](https://github.com/collerek/ormar/issues/502) * Fix timezone issues with datetime [#504](https://github.com/collerek/ormar/issues/504) * Remove literal binds in query generation to unblock postgres arrays [#/tophat/ormar-postgres-extensions/9](https://github.com/tophat/ormar-postgres-extensions/pull/9) +* Fix bulk update for `JSON` fields [#519](https://github.com/collerek/ormar/issues/519) ## 💬 Other +* Improve performance of `bulk_create` by bypassing `databases` `execute_many` suboptimal implementation. (by @Mng-dev-ai thanks!) [#520](https://github.com/collerek/ormar/pull/520) * Bump min. required `databases` version to `>=5.4`. - # 0.10.23 ## ✨ Features diff --git a/ormar/decorators/signals.py b/ormar/decorators/signals.py index cdb9f45..f70b5b7 100644 --- a/ormar/decorators/signals.py +++ b/ormar/decorators/signals.py @@ -173,9 +173,7 @@ def post_relation_remove( return receiver(signal="post_relation_remove", senders=senders) -def post_bulk_update( - senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def post_bulk_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for post_bulk_update signal. diff --git a/ormar/exceptions.py b/ormar/exceptions.py index 8fd6bab..12e2e3a 100644 --- a/ormar/exceptions.py +++ b/ormar/exceptions.py @@ -87,4 +87,5 @@ class ModelListEmptyError(AsyncOrmException): """ Raised for objects is empty when bulk_update """ + pass diff --git a/ormar/models/mixins/save_mixin.py b/ormar/models/mixins/save_mixin.py index 60efa37..89ddbfe 100644 --- a/ormar/models/mixins/save_mixin.py +++ b/ormar/models/mixins/save_mixin.py @@ -12,6 +12,11 @@ from typing import ( cast, ) +try: + import orjson as json +except ImportError: # pragma: no cover + import json # type: ignore + import pydantic import ormar # noqa: I100, I202 @@ -31,6 +36,8 @@ class SavePrepareMixin(RelationMixin, AliasMixin): if TYPE_CHECKING: # pragma: nocover _choices_fields: Optional[Set] _skip_ellipsis: Callable + _json_fields: Set[str] + _bytes_fields: Set[str] __fields__: Dict[str, pydantic.fields.ModelField] @classmethod @@ -53,6 +60,7 @@ class SavePrepareMixin(RelationMixin, AliasMixin): new_kwargs = cls.substitute_models_with_pks(new_kwargs) new_kwargs = cls.populate_default_values(new_kwargs) new_kwargs = cls.reconvert_str_to_bytes(new_kwargs) + new_kwargs = cls.dump_all_json_fields_to_str(new_kwargs) new_kwargs = cls.translate_columns_to_aliases(new_kwargs) return new_kwargs @@ -68,6 +76,7 @@ class SavePrepareMixin(RelationMixin, AliasMixin): new_kwargs = cls.parse_non_db_fields(new_kwargs) new_kwargs = cls.substitute_models_with_pks(new_kwargs) new_kwargs = cls.reconvert_str_to_bytes(new_kwargs) + new_kwargs = cls.dump_all_json_fields_to_str(new_kwargs) new_kwargs = cls.translate_columns_to_aliases(new_kwargs) return new_kwargs @@ -172,18 +181,13 @@ class SavePrepareMixin(RelationMixin, AliasMixin): :return: dictionary of model that is about to be saved :rtype: Dict """ - bytes_fields = { - name - for name, field in cls.Meta.model_fields.items() - if field.__type__ == bytes - } bytes_base64_fields = { name for name, field in cls.Meta.model_fields.items() if field.represent_as_base64_str } for key, value in model_dict.items(): - if key in bytes_fields and isinstance(value, str): + if key in cls._bytes_fields and isinstance(value, str): model_dict[key] = ( value.encode("utf-8") if key not in bytes_base64_fields @@ -191,6 +195,22 @@ class SavePrepareMixin(RelationMixin, AliasMixin): ) return model_dict + @classmethod + def dump_all_json_fields_to_str(cls, model_dict: Dict) -> Dict: + """ + Receives dictionary of model that is about to be saved and changes + all json fields into strings + + :param model_dict: dictionary of model that is about to be saved + :type model_dict: Dict + :return: dictionary of model that is about to be saved + :rtype: Dict + """ + for key, value in model_dict.items(): + if key in cls._json_fields and not isinstance(value, str): + model_dict[key] = json.dumps(value) + return model_dict + @classmethod def populate_default_values(cls, new_kwargs: Dict) -> Dict: """ diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 95cba43..103347d 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -30,8 +30,9 @@ except ImportError: # pragma: no cover import ormar # noqa I100 from ormar import MultipleMatches, NoMatch from ormar.exceptions import ( - ModelPersistenceError, QueryDefinitionError, - ModelListEmptyError + ModelPersistenceError, + QueryDefinitionError, + ModelListEmptyError, ) from ormar.queryset import FieldAccessor, FilterQuery, SelectAction from ormar.queryset.actions.order_action import OrderAction @@ -1063,10 +1064,7 @@ class QuerySet(Generic[T]): :param objects: list of ormar models already initialized and ready to save. :type objects: List[Model] """ - ready_objects = [ - obj.prepare_model_to_save(obj.dict()) - for obj in objects - ] + ready_objects = [obj.prepare_model_to_save(obj.dict()) for obj in objects] expr = self.table.insert().values(ready_objects) await self.database.execute(expr) @@ -1118,9 +1116,9 @@ class QuerySet(Generic[T]): f"{self.model.__name__} has to have {pk_name} filled." ) new_kwargs = obj.prepare_model_to_update(new_kwargs) - ready_objects.append({ - "new_" + k: v for k, v in new_kwargs.items() if k in columns - }) + ready_objects.append( + {"new_" + k: v for k, v in new_kwargs.items() if k in columns} + ) pk_column = self.model_meta.table.c.get(self.model.get_column_alias(pk_name)) pk_column_name = self.model.get_column_alias(pk_name) @@ -1146,4 +1144,3 @@ class QuerySet(Generic[T]): await cast(Type["Model"], self.model_cls).Meta.signals.post_bulk_update.send( sender=self.model_cls, instances=objects # type: ignore ) - diff --git a/ormar/signals/signal.py b/ormar/signals/signal.py index d5b4a5c..2b76787 100644 --- a/ormar/signals/signal.py +++ b/ormar/signals/signal.py @@ -77,7 +77,8 @@ class Signal: """ new_receiver_key = make_id(receiver) receiver_func: Union[Callable, None] = self._receivers.pop( - new_receiver_key, None) + new_receiver_key, None + ) return True if receiver_func is not None else False async def send(self, sender: Type["Model"], **kwargs: Any) -> None: @@ -100,6 +101,7 @@ class SignalEmitter(dict): Emitter that registers the signals in internal dictionary. If signal with given name does not exist it's auto added on access. """ + def __getattr__(self, item: str) -> Signal: return self.setdefault(item, Signal()) diff --git a/tests/test_queries/test_queryset_level_methods.py b/tests/test_queries/test_queryset_level_methods.py index e7a9543..449c7df 100644 --- a/tests/test_queries/test_queryset_level_methods.py +++ b/tests/test_queries/test_queryset_level_methods.py @@ -1,13 +1,15 @@ -from typing import Optional +from typing import List, Optional import databases +import pydantic import pytest import sqlalchemy import ormar from ormar.exceptions import ( - ModelPersistenceError, QueryDefinitionError, - ModelListEmptyError + ModelPersistenceError, + QueryDefinitionError, + ModelListEmptyError, ) from tests.settings import DATABASE_URL @@ -63,6 +65,17 @@ class Note(ormar.Model): category: Optional[Category] = ormar.ForeignKey(Category) +class ItemConfig(ormar.Model): + class Meta: + metadata = metadata + database = database + tablename = "item_config" + + id: Optional[int] = ormar.Integer(primary_key=True) + item_id: str = ormar.String(max_length=32, index=True) + pairs: pydantic.Json = ormar.JSON(default=["2", "3"]) + + @pytest.fixture(autouse=True, scope="module") def create_test_database(): engine = sqlalchemy.create_engine(DATABASE_URL) @@ -315,3 +328,23 @@ async def test_bulk_update_not_saved_objts(): with pytest.raises(ModelListEmptyError): await Note.objects.bulk_update([]) + + +@pytest.mark.asyncio +async def test_bulk_operations_with_json(): + async with database: + items = [ + ItemConfig(item_id="test1"), + ItemConfig(item_id="test2"), + ItemConfig(item_id="test3"), + ] + await ItemConfig.objects.bulk_create(items) + items = await ItemConfig.objects.all() + assert all(x.pairs == ["2", "3"] for x in items) + + for item in items: + item.pairs = ["1"] + + await ItemConfig.objects.bulk_update(items) + items = await ItemConfig.objects.all() + assert all(x.pairs == ["1"] for x in items) diff --git a/tests/test_signals/test_signals.py b/tests/test_signals/test_signals.py index 1002a2f..01e4f7a 100644 --- a/tests/test_signals/test_signals.py +++ b/tests/test_signals/test_signals.py @@ -7,8 +7,13 @@ import sqlalchemy import ormar from ormar import ( - post_bulk_update, post_delete, post_save, post_update, - pre_delete, pre_save, pre_update + post_bulk_update, + post_delete, + post_save, + post_update, + pre_delete, + pre_save, + pre_update, ) from ormar.signals import SignalEmitter from ormar.exceptions import SignalDefinitionError @@ -202,7 +207,9 @@ async def test_signal_functions(cleanup): await Album.objects.bulk_update(albums) - cnt = await AuditLog.objects.filter(event_type__contains="BULK_POST").count() + cnt = await AuditLog.objects.filter( + event_type__contains="BULK_POST" + ).count() assert cnt == len(albums) album.signals.bulk_post_update.disconnect(after_bulk_update)