Fix for prefetch related (#1275)

* fix prefetch related merging same relations refering to the same children models

* change to List for p3.8

* adapt refactored prefetch query from abandoned composite_key branch and make sure new test passes

* remove unused code, add missing test for prefetch related with self reference models
This commit is contained in:
collerek
2024-03-24 00:00:51 +01:00
committed by GitHub
parent 1ed0d5a87f
commit 52d992d8c7
13 changed files with 875 additions and 831 deletions

View File

@ -364,6 +364,64 @@ class ForeignKeyField(BaseField):
prefix = "to_" if self.self_reference else "" prefix = "to_" if self.self_reference else ""
return self.through_relation_name or f"{prefix}{self.owner.get_name()}" return self.through_relation_name or f"{prefix}{self.owner.get_name()}"
def get_filter_clause_target(self) -> Type["Model"]:
return self.to
def get_model_relation_fields(self, use_alias: bool = False) -> str:
"""
Extract names of the database columns or model fields that are connected
with given relation based on use_alias switch and which side of the relation
the current field is - reverse or normal.
:param use_alias: use db names aliases or model fields
:type use_alias: bool
:return: name or names of the related columns/ fields
:rtype: Union[str, List[str]]
"""
if use_alias:
return self._get_model_relation_fields_alias()
return self._get_model_relation_fields_name()
def _get_model_relation_fields_name(self) -> str:
if self.virtual:
return self.owner.ormar_config.pkname
return self.name
def _get_model_relation_fields_alias(self) -> str:
if self.virtual:
return self.owner.ormar_config.model_fields[
self.owner.ormar_config.pkname
].get_alias()
return self.get_alias()
def get_related_field_alias(self) -> str:
"""
Extract names of the related database columns or that are connected
with given relation based to use as a target in filter clause.
:return: name or names of the related columns/ fields
:rtype: Union[str, Dict[str, str]]
"""
if self.virtual:
field_name = self.get_related_name()
field = self.to.ormar_config.model_fields[field_name]
return field.get_alias()
target_field = self.to.get_column_alias(self.to.ormar_config.pkname)
return target_field
def get_related_field_name(self) -> Union[str, List[str]]:
"""
Returns name of the relation field that should be used in prefetch query.
This field is later used to register relation in prefetch query,
populate relations dict, and populate nested model in prefetch query.
:return: name(s) of the field
:rtype: Union[str, List[str]]
"""
if self.virtual:
return self.get_related_name()
return self.to.ormar_config.pkname
def _evaluate_forward_ref( def _evaluate_forward_ref(
self, globalns: Any, localns: Any, is_through: bool = False self, globalns: Any, localns: Any, is_through: bool = False
) -> None: ) -> None:

View File

@ -268,6 +268,51 @@ class ManyToManyField( # type: ignore
""" """
return self.through return self.through
def get_filter_clause_target(self) -> Type["Model"]:
return self.through
def get_model_relation_fields(self, use_alias: bool = False) -> str:
"""
Extract names of the database columns or model fields that are connected
with given relation based on use_alias switch.
:param use_alias: use db names aliases or model fields
:type use_alias: bool
:return: name or names of the related columns/ fields
:rtype: Union[str, List[str]]
"""
pk_field = self.owner.ormar_config.model_fields[self.owner.ormar_config.pkname]
result = pk_field.get_alias() if use_alias else pk_field.name
return result
def get_related_field_alias(self) -> str:
"""
Extract names of the related database columns or that are connected
with given relation based to use as a target in filter clause.
:return: name or names of the related columns/ fields
:rtype: Union[str, Dict[str, str]]
"""
if self.self_reference and self.self_reference_primary == self.name:
field_name = self.default_target_field_name()
else:
field_name = self.default_source_field_name()
sub_field = self.through.ormar_config.model_fields[field_name]
return sub_field.get_alias()
def get_related_field_name(self) -> Union[str, List[str]]:
"""
Returns name of the relation field that should be used in prefetch query.
This field is later used to register relation in prefetch query,
populate relations dict, and populate nested model in prefetch query.
:return: name(s) of the field
:rtype: Union[str, List[str]]
"""
if self.self_reference and self.self_reference_primary == self.name:
return self.default_target_field_name()
return self.default_source_field_name()
def create_default_through_model(self) -> None: def create_default_through_model(self) -> None:
""" """
Creates default empty through model if no additional fields are required. Creates default empty through model if no additional fields are required.

View File

@ -8,14 +8,12 @@ it became quite complicated over time.
from ormar.models.mixins.alias_mixin import AliasMixin from ormar.models.mixins.alias_mixin import AliasMixin
from ormar.models.mixins.excludable_mixin import ExcludableMixin from ormar.models.mixins.excludable_mixin import ExcludableMixin
from ormar.models.mixins.merge_mixin import MergeModelMixin from ormar.models.mixins.merge_mixin import MergeModelMixin
from ormar.models.mixins.prefetch_mixin import PrefetchQueryMixin
from ormar.models.mixins.pydantic_mixin import PydanticMixin from ormar.models.mixins.pydantic_mixin import PydanticMixin
from ormar.models.mixins.save_mixin import SavePrepareMixin from ormar.models.mixins.save_mixin import SavePrepareMixin
__all__ = [ __all__ = [
"MergeModelMixin", "MergeModelMixin",
"AliasMixin", "AliasMixin",
"PrefetchQueryMixin",
"SavePrepareMixin", "SavePrepareMixin",
"ExcludableMixin", "ExcludableMixin",
"PydanticMixin", "PydanticMixin",

View File

@ -1,123 +0,0 @@
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple, Type, cast
from ormar.models.mixins.relation_mixin import RelationMixin
if TYPE_CHECKING: # pragma: no cover
from ormar.fields import ForeignKeyField, ManyToManyField
class PrefetchQueryMixin(RelationMixin):
"""
Used in PrefetchQuery to extract ids and names of models to prefetch.
"""
if TYPE_CHECKING: # pragma no cover
from ormar import Model
get_name: Callable # defined in NewBaseModel
@staticmethod
def get_clause_target_and_filter_column_name(
parent_model: Type["Model"],
target_model: Type["Model"],
reverse: bool,
related: str,
) -> Tuple[Type["Model"], str]:
"""
Returns Model on which query clause should be performed and name of the column.
:param parent_model: related model that the relation lead to
:type parent_model: Type[Model]
:param target_model: model on which query should be performed
:type target_model: Type[Model]
:param reverse: flag if the relation is reverse
:type reverse: bool
:param related: name of the relation field
:type related: str
:return: Model on which query clause should be performed and name of the column
:rtype: Tuple[Type[Model], str]
"""
if reverse:
field_name = parent_model.ormar_config.model_fields[
related
].get_related_name()
field = target_model.ormar_config.model_fields[field_name]
if field.is_multi:
field = cast("ManyToManyField", field)
field_name = field.default_target_field_name()
sub_field = field.through.ormar_config.model_fields[field_name]
return field.through, sub_field.get_alias()
return target_model, field.get_alias()
target_field = target_model.get_column_alias(target_model.ormar_config.pkname)
return target_model, target_field
@staticmethod
def get_column_name_for_id_extraction(
parent_model: Type["Model"], reverse: bool, related: str, use_raw: bool
) -> str:
"""
Returns name of the column that should be used to extract ids from model.
Depending on the relation side it's either primary key column of parent model
or field name specified by related parameter.
:param parent_model: model from which id column should be extracted
:type parent_model: Type[Model]
:param reverse: flag if the relation is reverse
:type reverse: bool
:param related: name of the relation field
:type related: str
:param use_raw: flag if aliases or field names should be used
:type use_raw: bool
:return:
:rtype:
"""
if reverse:
column_name = parent_model.ormar_config.pkname
return (
parent_model.get_column_alias(column_name) if use_raw else column_name
)
column = parent_model.ormar_config.model_fields[related]
return column.get_alias() if use_raw else column.name
@classmethod
def get_related_field_name(cls, target_field: "ForeignKeyField") -> str:
"""
Returns name of the relation field that should be used in prefetch query.
This field is later used to register relation in prefetch query,
populate relations dict, and populate nested model in prefetch query.
:param target_field: relation field that should be used in prefetch
:type target_field: Type[BaseField]
:return: name of the field
:rtype: str
"""
if target_field.is_multi:
return cls.get_name()
if target_field.virtual:
return target_field.get_related_name()
return target_field.to.ormar_config.pkname
@classmethod
def get_filtered_names_to_extract(cls, prefetch_dict: Dict) -> List:
"""
Returns list of related fields names that should be followed to prefetch related
models from.
List of models is translated into dict to assure each model is extracted only
once in one query, that's why this function accepts prefetch_dict not list.
Only relations from current model are returned.
:param prefetch_dict: dictionary of fields to extract
:type prefetch_dict: Dict
:return: list of fields names to extract
:rtype: List
"""
related_to_extract = []
if prefetch_dict and prefetch_dict is not Ellipsis:
related_to_extract = [
related
for related in cls.extract_related_names()
if related in prefetch_dict
]
return related_to_extract

View File

@ -1,14 +1,12 @@
from ormar.models.mixins import ( from ormar.models.mixins import (
ExcludableMixin, ExcludableMixin,
MergeModelMixin, MergeModelMixin,
PrefetchQueryMixin,
PydanticMixin, PydanticMixin,
SavePrepareMixin, SavePrepareMixin,
) )
class ModelTableProxy( class ModelTableProxy(
PrefetchQueryMixin,
MergeModelMixin, MergeModelMixin,
SavePrepareMixin, SavePrepareMixin,
ExcludableMixin, ExcludableMixin,

View File

@ -25,7 +25,6 @@ import typing_extensions
import ormar # noqa I100 import ormar # noqa I100
from ormar.exceptions import ModelError, ModelPersistenceError from ormar.exceptions import ModelError, ModelPersistenceError
from ormar.fields import BaseField
from ormar.fields.foreign_key import ForeignKeyField from ormar.fields.foreign_key import ForeignKeyField
from ormar.fields.parsers import decode_bytes, encode_json from ormar.fields.parsers import decode_bytes, encode_json
from ormar.models.helpers import register_relation_in_alias_manager from ormar.models.helpers import register_relation_in_alias_manager
@ -1167,18 +1166,3 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
f"model without pk set!" f"model without pk set!"
) )
return self_fields return self_fields
def get_relation_model_id(self, target_field: "BaseField") -> Optional[int]:
"""
Returns an id of the relation side model to use in prefetch query.
:param target_field: field with relation definition
:type target_field: "BaseField"
:return: value of pk if set
:rtype: Optional[int]
"""
if target_field.virtual or target_field.is_multi:
return self.pk
related_name = target_field.name
related_model = getattr(self, related_name)
return None if not related_model else related_model.pk

File diff suppressed because it is too large Load Diff

View File

@ -172,7 +172,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
orders_by=self.order_bys, orders_by=self.order_bys,
) )
return await query.prefetch_related(models=models, rows=rows) # type: ignore return await query.prefetch_related(models=models) # type: ignore
async def _process_query_result_rows(self, rows: List) -> List["T"]: async def _process_query_result_rows(self, rows: List) -> List["T"]:
""" """

View File

@ -6,7 +6,6 @@ from typing import (
Dict, Dict,
List, List,
Optional, Optional,
Sequence,
Set, Set,
Tuple, Tuple,
Type, Type,
@ -42,7 +41,7 @@ def check_node_not_dict_or_not_last_node(
def translate_list_to_dict( # noqa: CCR001 def translate_list_to_dict( # noqa: CCR001
list_to_trans: Union[List, Set], is_order: bool = False list_to_trans: Union[List, Set], default: Any = ...
) -> Dict: ) -> Dict:
""" """
Splits the list of strings by '__' and converts them to dictionary with nested Splits the list of strings by '__' and converts them to dictionary with nested
@ -53,6 +52,8 @@ def translate_list_to_dict( # noqa: CCR001
:param list_to_trans: input list :param list_to_trans: input list
:type list_to_trans: Union[List, Set] :type list_to_trans: Union[List, Set]
:param default: value to use as a default value
:type default: Any
:param is_order: flag if change affects order_by clauses are they require special :param is_order: flag if change affects order_by clauses are they require special
default value with sort order. default value with sort order.
:type is_order: bool :type is_order: bool
@ -63,14 +64,7 @@ def translate_list_to_dict( # noqa: CCR001
for path in list_to_trans: for path in list_to_trans:
current_level = new_dict current_level = new_dict
parts = path.split("__") parts = path.split("__")
def_val: Any = ... def_val: Any = default
if is_order:
if parts[0][0] == "-":
def_val = "desc"
parts[0] = parts[0][1:]
else:
def_val = "asc"
for ind, part in enumerate(parts): for ind, part in enumerate(parts):
is_last = ind == len(parts) - 1 is_last = ind == len(parts) - 1
if check_node_not_dict_or_not_last_node( if check_node_not_dict_or_not_last_node(
@ -189,78 +183,6 @@ def update_dict_from_list(curr_dict: Dict, list_to_update: Union[List, Set]) ->
return updated_dict return updated_dict
def extract_nested_models( # noqa: CCR001
model: "Model", model_type: Type["Model"], select_dict: Dict, extracted: Dict
) -> None:
"""
Iterates over model relations and extracts all nested models from select_dict and
puts them in corresponding list under relation name in extracted dict.keys
Basically flattens all relation to dictionary of all related models, that can be
used on several models and extract all of their children into dictionary of lists
witch children models.
Goes also into nested relations if needed (specified in select_dict).
:param model: parent Model
:type model: Model
:param model_type: parent model class
:type model_type: Type[Model]
:param select_dict: dictionary of related models from select_related
:type select_dict: Dict
:param extracted: dictionary with already extracted models
:type extracted: Dict
"""
follow = [rel for rel in model_type.extract_related_names() if rel in select_dict]
for related in follow:
child = getattr(model, related)
if not child:
continue
target_model = model_type.ormar_config.model_fields[related].to
if isinstance(child, list):
extracted.setdefault(target_model.get_name(), []).extend(child)
if select_dict[related] is not Ellipsis:
for sub_child in child:
extract_nested_models(
sub_child, target_model, select_dict[related], extracted
)
else:
extracted.setdefault(target_model.get_name(), []).append(child)
if select_dict[related] is not Ellipsis:
extract_nested_models(
child, target_model, select_dict[related], extracted
)
def extract_models_to_dict_of_lists(
model_type: Type["Model"],
models: Sequence["Model"],
select_dict: Dict,
extracted: Optional[Dict] = None,
) -> Dict:
"""
Receives a list of models and extracts all of the children and their children
into dictionary of lists with children models, flattening the structure to one dict
with all children models under their relation keys.
:param model_type: parent model class
:type model_type: Type[Model]
:param models: list of models from which related models should be extracted.
:type models: List[Model]
:param select_dict: dictionary of related models from select_related
:type select_dict: Dict
:param extracted: dictionary with already extracted models
:type extracted: Dict
:return: dictionary of lists f related models
:rtype: Dict
"""
if not extracted:
extracted = dict()
for model in models:
extract_nested_models(model, model_type, select_dict, extracted)
return extracted
def get_relationship_alias_model_and_str( def get_relationship_alias_model_and_str(
source_model: Type["Model"], related_parts: List source_model: Type["Model"], related_parts: List
) -> Tuple[str, Type["Model"], str, bool]: ) -> Tuple[str, Type["Model"], str, bool]:

150
poetry.lock generated
View File

@ -1,9 +1,10 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiomysql" name = "aiomysql"
version = "0.2.0" version = "0.2.0"
description = "MySQL driver for asyncio." description = "MySQL driver for asyncio."
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -22,6 +23,7 @@ sa = ["sqlalchemy (>=1.3,<1.4)"]
name = "aiopg" name = "aiopg"
version = "1.4.0" version = "1.4.0"
description = "Postgres integration with asyncio." description = "Postgres integration with asyncio."
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -40,6 +42,7 @@ sa = ["sqlalchemy[postgresql-psycopg2binary] (>=1.3,<1.5)"]
name = "aiosqlite" name = "aiosqlite"
version = "0.19.0" version = "0.19.0"
description = "asyncio bridge to the standard sqlite3 module" description = "asyncio bridge to the standard sqlite3 module"
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -55,6 +58,7 @@ docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"]
name = "annotated-types" name = "annotated-types"
version = "0.6.0" version = "0.6.0"
description = "Reusable constraint types to use with typing.Annotated" description = "Reusable constraint types to use with typing.Annotated"
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -69,6 +73,7 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
name = "anyio" name = "anyio"
version = "4.3.0" version = "4.3.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations" description = "High level compatibility layer for multiple asynchronous event loop implementations"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -91,6 +96,7 @@ trio = ["trio (>=0.23)"]
name = "asgi-lifespan" name = "asgi-lifespan"
version = "2.1.0" version = "2.1.0"
description = "Programmatic startup/shutdown of ASGI apps." description = "Programmatic startup/shutdown of ASGI apps."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -105,6 +111,7 @@ sniffio = "*"
name = "astunparse" name = "astunparse"
version = "1.6.3" version = "1.6.3"
description = "An AST unparser for Python" description = "An AST unparser for Python"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -120,6 +127,7 @@ wheel = ">=0.23.0,<1.0"
name = "async-timeout" name = "async-timeout"
version = "4.0.3" version = "4.0.3"
description = "Timeout context manager for asyncio programs" description = "Timeout context manager for asyncio programs"
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -131,6 +139,7 @@ files = [
name = "asyncpg" name = "asyncpg"
version = "0.28.0" version = "0.28.0"
description = "An asyncio PostgreSQL driver" description = "An asyncio PostgreSQL driver"
category = "main"
optional = true optional = true
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
@ -184,6 +193,7 @@ test = ["flake8 (>=5.0,<6.0)", "uvloop (>=0.15.3)"]
name = "babel" name = "babel"
version = "2.14.0" version = "2.14.0"
description = "Internationalization utilities" description = "Internationalization utilities"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -201,6 +211,7 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
name = "black" name = "black"
version = "24.3.0" version = "24.3.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -247,6 +258,7 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "certifi" name = "certifi"
version = "2024.2.2" version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -258,6 +270,7 @@ files = [
name = "cffi" name = "cffi"
version = "1.16.0" version = "1.16.0"
description = "Foreign Function Interface for Python calling C code." description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -322,6 +335,7 @@ pycparser = "*"
name = "cfgv" name = "cfgv"
version = "3.4.0" version = "3.4.0"
description = "Validate configuration and produce human readable error messages." description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -333,6 +347,7 @@ files = [
name = "charset-normalizer" name = "charset-normalizer"
version = "3.3.2" version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
@ -432,6 +447,7 @@ files = [
name = "click" name = "click"
version = "8.1.7" version = "8.1.7"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -446,6 +462,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "codecov" name = "codecov"
version = "2.1.13" version = "2.1.13"
description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
@ -461,6 +478,7 @@ requests = ">=2.7.9"
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [ files = [
@ -472,6 +490,7 @@ files = [
name = "coverage" name = "coverage"
version = "7.4.4" version = "7.4.4"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -539,6 +558,7 @@ toml = ["tomli"]
name = "cryptography" name = "cryptography"
version = "42.0.5" version = "42.0.5"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -593,6 +613,7 @@ test-randomorder = ["pytest-randomly"]
name = "databases" name = "databases"
version = "0.7.0" version = "0.7.0"
description = "Async database support for Python." description = "Async database support for Python."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -617,6 +638,7 @@ sqlite = ["aiosqlite"]
name = "dataclasses" name = "dataclasses"
version = "0.6" version = "0.6"
description = "A backport of the dataclasses module for Python 3.6" description = "A backport of the dataclasses module for Python 3.6"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -628,6 +650,7 @@ files = [
name = "distlib" name = "distlib"
version = "0.3.8" version = "0.3.8"
description = "Distribution utilities" description = "Distribution utilities"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -639,6 +662,7 @@ files = [
name = "exceptiongroup" name = "exceptiongroup"
version = "1.2.0" version = "1.2.0"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -649,10 +673,27 @@ files = [
[package.extras] [package.extras]
test = ["pytest (>=6)"] test = ["pytest (>=6)"]
[[package]]
name = "faker"
version = "24.3.0"
description = "Faker is a Python package that generates fake data for you."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "Faker-24.3.0-py3-none-any.whl", hash = "sha256:9978025e765ba79f8bf6154c9630a9c2b7f9c9b0f175d4ad5e04b19a82a8d8d6"},
{file = "Faker-24.3.0.tar.gz", hash = "sha256:5fb5aa9749d09971e04a41281ae3ceda9414f683d4810a694f8a8eebb8f9edec"},
]
[package.dependencies]
python-dateutil = ">=2.4"
typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\""}
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.109.2" version = "0.109.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -672,6 +713,7 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)"
name = "filelock" name = "filelock"
version = "3.13.1" version = "3.13.1"
description = "A platform independent file lock." description = "A platform independent file lock."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -688,6 +730,7 @@ typing = ["typing-extensions (>=4.8)"]
name = "ghp-import" name = "ghp-import"
version = "2.1.0" version = "2.1.0"
description = "Copy your docs directly to the gh-pages branch." description = "Copy your docs directly to the gh-pages branch."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -705,6 +748,7 @@ dev = ["flake8", "markdown", "twine", "wheel"]
name = "greenlet" name = "greenlet"
version = "3.0.3" version = "3.0.3"
description = "Lightweight in-process concurrent programming" description = "Lightweight in-process concurrent programming"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -776,6 +820,7 @@ test = ["objgraph", "psutil"]
name = "griffe" name = "griffe"
version = "0.42.1" version = "0.42.1"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -791,6 +836,7 @@ colorama = ">=0.4"
name = "h11" name = "h11"
version = "0.14.0" version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -802,6 +848,7 @@ files = [
name = "httpcore" name = "httpcore"
version = "0.17.3" version = "0.17.3"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -813,16 +860,17 @@ files = [
anyio = ">=3.0,<5.0" anyio = ">=3.0,<5.0"
certifi = "*" certifi = "*"
h11 = ">=0.13,<0.15" h11 = ">=0.13,<0.15"
sniffio = "==1.*" sniffio = ">=1.0.0,<2.0.0"
[package.extras] [package.extras]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.24.1" version = "0.24.1"
description = "The next generation HTTP client." description = "The next generation HTTP client."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -838,14 +886,15 @@ sniffio = "*"
[package.extras] [package.extras]
brotli = ["brotli", "brotlicffi"] brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.5.35" version = "2.5.35"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -860,6 +909,7 @@ license = ["ukkonen"]
name = "idna" name = "idna"
version = "3.6" version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -871,6 +921,7 @@ files = [
name = "importlib-metadata" name = "importlib-metadata"
version = "7.1.0" version = "7.1.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -890,6 +941,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)",
name = "importlib-resources" name = "importlib-resources"
version = "6.4.0" version = "6.4.0"
description = "Read resources from Python packages" description = "Read resources from Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -908,6 +960,7 @@ testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "p
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -919,6 +972,7 @@ files = [
name = "jinja2" name = "jinja2"
version = "3.1.3" version = "3.1.3"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -936,6 +990,7 @@ i18n = ["Babel (>=2.7)"]
name = "markdown" name = "markdown"
version = "3.6" version = "3.6"
description = "Python implementation of John Gruber's Markdown." description = "Python implementation of John Gruber's Markdown."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -954,6 +1009,7 @@ testing = ["coverage", "pyyaml"]
name = "markupsafe" name = "markupsafe"
version = "2.1.5" version = "2.1.5"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1023,6 +1079,7 @@ files = [
name = "mergedeep" name = "mergedeep"
version = "1.3.4" version = "1.3.4"
description = "A deep merge function for 🐍." description = "A deep merge function for 🐍."
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1034,6 +1091,7 @@ files = [
name = "mike" name = "mike"
version = "2.0.0" version = "2.0.0"
description = "Manage multiple versions of your MkDocs-powered documentation" description = "Manage multiple versions of your MkDocs-powered documentation"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1058,6 +1116,7 @@ test = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"]
name = "mkdocs" name = "mkdocs"
version = "1.5.3" version = "1.5.3"
description = "Project documentation with Markdown." description = "Project documentation with Markdown."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1089,6 +1148,7 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
name = "mkdocs-autorefs" name = "mkdocs-autorefs"
version = "1.0.1" version = "1.0.1"
description = "Automatically link across pages in MkDocs." description = "Automatically link across pages in MkDocs."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1105,6 +1165,7 @@ mkdocs = ">=1.1"
name = "mkdocs-gen-files" name = "mkdocs-gen-files"
version = "0.5.0" version = "0.5.0"
description = "MkDocs plugin to programmatically generate documentation pages during the build" description = "MkDocs plugin to programmatically generate documentation pages during the build"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1119,6 +1180,7 @@ mkdocs = ">=1.0.3"
name = "mkdocs-literate-nav" name = "mkdocs-literate-nav"
version = "0.6.1" version = "0.6.1"
description = "MkDocs plugin to specify the navigation in Markdown instead of YAML" description = "MkDocs plugin to specify the navigation in Markdown instead of YAML"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1133,6 +1195,7 @@ mkdocs = ">=1.0.3"
name = "mkdocs-material" name = "mkdocs-material"
version = "9.2.8" version = "9.2.8"
description = "Documentation that simply works" description = "Documentation that simply works"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1157,6 +1220,7 @@ requests = ">=2.31,<3.0"
name = "mkdocs-material-extensions" name = "mkdocs-material-extensions"
version = "1.3.1" version = "1.3.1"
description = "Extension pack for Python Markdown and MkDocs Material." description = "Extension pack for Python Markdown and MkDocs Material."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1168,6 +1232,7 @@ files = [
name = "mkdocs-section-index" name = "mkdocs-section-index"
version = "0.3.8" version = "0.3.8"
description = "MkDocs plugin to allow clickable sections that lead to an index page" description = "MkDocs plugin to allow clickable sections that lead to an index page"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1182,6 +1247,7 @@ mkdocs = ">=1.2"
name = "mkdocstrings" name = "mkdocstrings"
version = "0.22.0" version = "0.22.0"
description = "Automatic documentation from sources, for MkDocs." description = "Automatic documentation from sources, for MkDocs."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1209,6 +1275,7 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
name = "mkdocstrings-python" name = "mkdocstrings-python"
version = "1.8.0" version = "1.8.0"
description = "A Python handler for mkdocstrings." description = "A Python handler for mkdocstrings."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1224,6 +1291,7 @@ mkdocstrings = ">=0.20"
name = "mypy" name = "mypy"
version = "1.9.0" version = "1.9.0"
description = "Optional static typing for Python" description = "Optional static typing for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1271,6 +1339,7 @@ reports = ["lxml"]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker." description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1282,6 +1351,7 @@ files = [
name = "mysqlclient" name = "mysqlclient"
version = "2.2.4" version = "2.2.4"
description = "Python interface to MySQL" description = "Python interface to MySQL"
category = "main"
optional = true optional = true
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1300,6 +1370,7 @@ files = [
name = "nest-asyncio" name = "nest-asyncio"
version = "1.6.0" version = "1.6.0"
description = "Patch asyncio to allow nested event loops" description = "Patch asyncio to allow nested event loops"
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1311,6 +1382,7 @@ files = [
name = "nodeenv" name = "nodeenv"
version = "1.8.0" version = "1.8.0"
description = "Node.js virtual environment builder" description = "Node.js virtual environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [ files = [
@ -1325,6 +1397,7 @@ setuptools = "*"
name = "orjson" name = "orjson"
version = "3.9.15" version = "3.9.15"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
category = "main"
optional = true optional = true
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1384,6 +1457,7 @@ files = [
name = "packaging" name = "packaging"
version = "24.0" version = "24.0"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1395,6 +1469,7 @@ files = [
name = "paginate" name = "paginate"
version = "0.5.6" version = "0.5.6"
description = "Divides large result sets into pages for easier browsing" description = "Divides large result sets into pages for easier browsing"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1405,6 +1480,7 @@ files = [
name = "pathspec" name = "pathspec"
version = "0.12.1" version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1416,6 +1492,7 @@ files = [
name = "platformdirs" name = "platformdirs"
version = "4.2.0" version = "4.2.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1431,6 +1508,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-
name = "pluggy" name = "pluggy"
version = "1.4.0" version = "1.4.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1446,6 +1524,7 @@ testing = ["pytest", "pytest-benchmark"]
name = "pre-commit" name = "pre-commit"
version = "2.21.0" version = "2.21.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1464,6 +1543,7 @@ virtualenv = ">=20.10.0"
name = "psycopg2-binary" name = "psycopg2-binary"
version = "2.9.9" version = "2.9.9"
description = "psycopg2 - Python-PostgreSQL Database Adapter" description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1545,6 +1625,7 @@ files = [
name = "py-cpuinfo" name = "py-cpuinfo"
version = "9.0.0" version = "9.0.0"
description = "Get CPU info with pure Python" description = "Get CPU info with pure Python"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1556,6 +1637,7 @@ files = [
name = "pycparser" name = "pycparser"
version = "2.21" version = "2.21"
description = "C parser in Python" description = "C parser in Python"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
@ -1567,6 +1649,7 @@ files = [
name = "pydantic" name = "pydantic"
version = "2.5.3" version = "2.5.3"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1586,6 +1669,7 @@ email = ["email-validator (>=2.0.0)"]
name = "pydantic-core" name = "pydantic-core"
version = "2.14.6" version = "2.14.6"
description = "" description = ""
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1703,6 +1787,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
name = "pydantic-extra-types" name = "pydantic-extra-types"
version = "2.6.0" version = "2.6.0"
description = "Extra Pydantic types." description = "Extra Pydantic types."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1720,6 +1805,7 @@ all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)",
name = "pygments" name = "pygments"
version = "2.17.2" version = "2.17.2"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1735,6 +1821,7 @@ windows-terminal = ["colorama (>=0.4.6)"]
name = "pymdown-extensions" name = "pymdown-extensions"
version = "10.7.1" version = "10.7.1"
description = "Extension pack for Python Markdown." description = "Extension pack for Python Markdown."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -1753,6 +1840,7 @@ extra = ["pygments (>=2.12)"]
name = "pymysql" name = "pymysql"
version = "1.1.0" version = "1.1.0"
description = "Pure Python MySQL Driver" description = "Pure Python MySQL Driver"
category = "main"
optional = true optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1768,6 +1856,7 @@ rsa = ["cryptography"]
name = "pyparsing" name = "pyparsing"
version = "3.1.2" version = "3.1.2"
description = "pyparsing module - Classes and methods to define and execute parsing grammars" description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false optional = false
python-versions = ">=3.6.8" python-versions = ">=3.6.8"
files = [ files = [
@ -1782,6 +1871,7 @@ diagrams = ["jinja2", "railroad-diagrams"]
name = "pytest" name = "pytest"
version = "7.4.4" version = "7.4.4"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1804,6 +1894,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "pytest-asyncio" name = "pytest-asyncio"
version = "0.21.1" version = "0.21.1"
description = "Pytest support for asyncio" description = "Pytest support for asyncio"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1822,6 +1913,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy
name = "pytest-benchmark" name = "pytest-benchmark"
version = "4.0.0" version = "4.0.0"
description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1842,6 +1934,7 @@ histogram = ["pygal", "pygaljs"]
name = "pytest-codspeed" name = "pytest-codspeed"
version = "2.2.1" version = "2.2.1"
description = "Pytest plugin to create CodSpeed benchmarks" description = "Pytest plugin to create CodSpeed benchmarks"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1864,6 +1957,7 @@ test = ["pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)"]
name = "pytest-cov" name = "pytest-cov"
version = "4.1.0" version = "4.1.0"
description = "Pytest plugin for measuring coverage." description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1882,6 +1976,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module" description = "Extensions to the standard Python datetime module"
category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [ files = [
@ -1896,6 +1991,7 @@ six = ">=1.5"
name = "pytz" name = "pytz"
version = "2024.1" version = "2024.1"
description = "World timezone definitions, modern and historical" description = "World timezone definitions, modern and historical"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1907,6 +2003,7 @@ files = [
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.1"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1915,7 +2012,6 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@ -1923,16 +2019,8 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@ -1949,7 +2037,6 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@ -1957,7 +2044,6 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@ -1967,6 +2053,7 @@ files = [
name = "pyyaml-env-tag" name = "pyyaml-env-tag"
version = "0.1" version = "0.1"
description = "A custom YAML tag for referencing environment variables in YAML files. " description = "A custom YAML tag for referencing environment variables in YAML files. "
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1981,6 +2068,7 @@ pyyaml = "*"
name = "regex" name = "regex"
version = "2023.12.25" version = "2023.12.25"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2083,6 +2171,7 @@ files = [
name = "requests" name = "requests"
version = "2.31.0" version = "2.31.0"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2104,6 +2193,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "ruff" name = "ruff"
version = "0.0.275" version = "0.0.275"
description = "An extremely fast Python linter, written in Rust." description = "An extremely fast Python linter, written in Rust."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2130,6 +2220,7 @@ files = [
name = "setuptools" name = "setuptools"
version = "69.2.0" version = "69.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2146,6 +2237,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
@ -2157,6 +2249,7 @@ files = [
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
description = "Sniff out which async library your code is running under" description = "Sniff out which async library your code is running under"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2168,6 +2261,7 @@ files = [
name = "sqlalchemy" name = "sqlalchemy"
version = "1.4.52" version = "1.4.52"
description = "Database Abstraction Library" description = "Database Abstraction Library"
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [ files = [
@ -2220,7 +2314,7 @@ files = [
] ]
[package.dependencies] [package.dependencies]
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""}
[package.extras] [package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
@ -2247,6 +2341,7 @@ sqlcipher = ["sqlcipher3_binary"]
name = "starlette" name = "starlette"
version = "0.36.3" version = "0.36.3"
description = "The little ASGI library that shines." description = "The little ASGI library that shines."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2265,6 +2360,7 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2276,6 +2372,7 @@ files = [
name = "types-aiofiles" name = "types-aiofiles"
version = "23.2.0.20240311" version = "23.2.0.20240311"
description = "Typing stubs for aiofiles" description = "Typing stubs for aiofiles"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2287,6 +2384,7 @@ files = [
name = "types-cryptography" name = "types-cryptography"
version = "3.3.23.2" version = "3.3.23.2"
description = "Typing stubs for cryptography" description = "Typing stubs for cryptography"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2298,6 +2396,7 @@ files = [
name = "types-enum34" name = "types-enum34"
version = "1.1.8" version = "1.1.8"
description = "Typing stubs for enum34" description = "Typing stubs for enum34"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2309,6 +2408,7 @@ files = [
name = "types-ipaddress" name = "types-ipaddress"
version = "1.0.8" version = "1.0.8"
description = "Typing stubs for ipaddress" description = "Typing stubs for ipaddress"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2320,6 +2420,7 @@ files = [
name = "types-orjson" name = "types-orjson"
version = "3.6.2" version = "3.6.2"
description = "Typing stubs for orjson" description = "Typing stubs for orjson"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2331,6 +2432,7 @@ files = [
name = "types-pkg-resources" name = "types-pkg-resources"
version = "0.1.3" version = "0.1.3"
description = "Typing stubs for pkg_resources" description = "Typing stubs for pkg_resources"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2342,6 +2444,7 @@ files = [
name = "types-pymysql" name = "types-pymysql"
version = "1.1.0.1" version = "1.1.0.1"
description = "Typing stubs for PyMySQL" description = "Typing stubs for PyMySQL"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2353,6 +2456,7 @@ files = [
name = "types-requests" name = "types-requests"
version = "2.31.0.20240311" version = "2.31.0.20240311"
description = "Typing stubs for requests" description = "Typing stubs for requests"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2367,6 +2471,7 @@ urllib3 = ">=2"
name = "types-toml" name = "types-toml"
version = "0.10.8.20240310" version = "0.10.8.20240310"
description = "Typing stubs for toml" description = "Typing stubs for toml"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2378,6 +2483,7 @@ files = [
name = "types-ujson" name = "types-ujson"
version = "5.9.0.0" version = "5.9.0.0"
description = "Typing stubs for ujson" description = "Typing stubs for ujson"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2389,6 +2495,7 @@ files = [
name = "typing-extensions" name = "typing-extensions"
version = "4.10.0" version = "4.10.0"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2400,6 +2507,7 @@ files = [
name = "urllib3" name = "urllib3"
version = "2.2.1" version = "2.2.1"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2417,6 +2525,7 @@ zstd = ["zstandard (>=0.18.0)"]
name = "verspec" name = "verspec"
version = "0.1.0" version = "0.1.0"
description = "Flexible version handling" description = "Flexible version handling"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -2431,6 +2540,7 @@ test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"]
name = "virtualenv" name = "virtualenv"
version = "20.25.1" version = "20.25.1"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2451,6 +2561,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
name = "watchdog" name = "watchdog"
version = "3.0.0" version = "3.0.0"
description = "Filesystem events monitoring" description = "Filesystem events monitoring"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2490,6 +2601,7 @@ watchmedo = ["PyYAML (>=3.10)"]
name = "wheel" name = "wheel"
version = "0.43.0" version = "0.43.0"
description = "A built-package format for Python" description = "A built-package format for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2504,6 +2616,7 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
name = "yappi" name = "yappi"
version = "1.6.0" version = "1.6.0"
description = "Yet Another Python Profiler" description = "Yet Another Python Profiler"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -2566,6 +2679,7 @@ test = ["gevent (>=20.6.2)"]
name = "zipp" name = "zipp"
version = "3.18.1" version = "3.18.1"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -2590,4 +2704,4 @@ sqlite = ["aiosqlite"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8.0" python-versions = "^3.8.0"
content-hash = "57525b944571556e307f2795f2d74c63017b4e3fbc10a82f30f323f91f8163e8" content-hash = "69ac3f442f88e777aeb77154e45fdd3d000cf3eedcfaca1d6b82e2fd568ceb44"

View File

@ -128,6 +128,7 @@ pydantic-extra-types = "^2.5.0"
watchdog = "<4.0.0" watchdog = "<4.0.0"
pytest-codspeed = "^2.2.0" pytest-codspeed = "^2.2.0"
mike = "^2.0.0" mike = "^2.0.0"
faker = "^24.3.0"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View File

@ -0,0 +1,119 @@
from random import randint
from typing import ForwardRef, Optional
import ormar
import pytest
from faker import Faker
from ormar.relations.relation_proxy import RelationProxy
from tests.lifespan import init_tests
from tests.settings import create_config
base_ormar_config = create_config()
fake = Faker()
class Author(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="authors")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=256)
class BookAuthor(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="book_authors")
id: int = ormar.Integer(primary_key=True)
class BookCoAuthor(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="book_co_authors")
id: int = ormar.Integer(primary_key=True)
class Book(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="books")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=256)
description: Optional[str] = ormar.String(max_length=256, nullable=True)
authors: RelationProxy[Author] = ormar.ManyToMany(
Author, related_name="author_books", through=BookAuthor
)
co_authors: RelationProxy[Author] = ormar.ManyToMany(
Author, related_name="co_author_books", through=BookCoAuthor
)
class SelfRef(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="selfrefs")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
main_child = ormar.ForeignKey(to=ForwardRef("SelfRef"), related_name="parent")
children: RelationProxy["SelfRef"] = ormar.ManyToMany(ForwardRef("SelfRef"))
SelfRef.update_forward_refs()
create_test_database = init_tests(base_ormar_config)
@pytest.mark.asyncio
async def test_prefetch_related_with_same_model_relations() -> None:
async with base_ormar_config.database:
for _ in range(6):
await Author.objects.create(name=fake.name())
book = await Book.objects.create(name=fake.sentence(nb_words=randint(1, 4)))
for i in range(1, 3):
await book.authors.add(await Author.objects.get(id=i))
for i in range(3, 6):
await book.co_authors.add(await Author.objects.get(id=i))
prefetch_result = await Book.objects.prefetch_related(
["authors", "co_authors"]
).all()
prefetch_dict_result = [x.dict() for x in prefetch_result if x.id == 1][0]
select_result = await Book.objects.select_related(
["authors", "co_authors"]
).all()
select_dict_result = [
x.dict(
exclude={
"authors": {"bookauthor": ...},
"co_authors": {"bookcoauthor": ...},
}
)
for x in select_result
if x.id == 1
][0]
assert prefetch_dict_result == select_dict_result
@pytest.mark.asyncio
async def test_prefetch_related_with_self_referencing() -> None:
async with base_ormar_config.database:
main_child = await SelfRef.objects.create(name="MainChild")
main = await SelfRef.objects.create(name="Main", main_child=main_child)
child1 = await SelfRef.objects.create(name="Child1")
child2 = await SelfRef.objects.create(name="Child2")
await main.children.add(child1)
await main.children.add(child2)
select_result = await SelfRef.objects.select_related(
["main_child", "children"]
).get(name="Main")
print(select_result.json(indent=4))
prefetch_result = await SelfRef.objects.prefetch_related(
["main_child", "children"]
).get(name="Main")
assert prefetch_result.main_child.name == main_child.name
assert len(prefetch_result.children) == 2
assert prefetch_result.children[0].name == child1.name
assert prefetch_result.children[1].name == child2.name

View File

@ -1,5 +1,3 @@
import ormar
from ormar.queryset.queries.prefetch_query import sort_models
from ormar.queryset.utils import ( from ormar.queryset.utils import (
subtract_dict, subtract_dict,
translate_list_to_dict, translate_list_to_dict,
@ -7,7 +5,6 @@ from ormar.queryset.utils import (
update_dict_from_list, update_dict_from_list,
) )
from tests.lifespan import init_tests
from tests.settings import create_config from tests.settings import create_config
base_ormar_config = create_config() base_ormar_config = create_config()
@ -172,39 +169,3 @@ def test_subtracting_with_set_and_dict():
} }
test = subtract_dict(curr_dict, dict_to_update) test = subtract_dict(curr_dict, dict_to_update)
assert test == {"translation": {"translations": {"language": Ellipsis}}} assert test == {"translation": {"translations": {"language": Ellipsis}}}
class SortModel(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="sorts")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
sort_order: int = ormar.Integer()
def test_sorting_models():
models = [
SortModel(id=1, name="Alice", sort_order=0),
SortModel(id=2, name="Al", sort_order=1),
SortModel(id=3, name="Zake", sort_order=1),
SortModel(id=4, name="Will", sort_order=0),
SortModel(id=5, name="Al", sort_order=2),
SortModel(id=6, name="Alice", sort_order=2),
]
orders_by = {"name": "asc", "none": {}, "sort_order": "desc"}
models = sort_models(models, orders_by)
assert models[5].name == "Zake"
assert models[0].name == "Al"
assert models[1].name == "Al"
assert [model.id for model in models] == [5, 2, 6, 1, 4, 3]
orders_by = {"name": "asc", "none": set("aa"), "id": "asc"}
models = sort_models(models, orders_by)
assert [model.id for model in models] == [2, 5, 1, 6, 4, 3]
orders_by = {"sort_order": "asc", "none": ..., "id": "asc", "uu": 2, "aa": None}
models = sort_models(models, orders_by)
assert [model.id for model in models] == [1, 4, 2, 3, 5, 6]
create_test_database = init_tests(base_ormar_config)