add most of the docstrings
This commit is contained in:
@ -122,7 +122,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
:param kwargs: all other args to be populated by BaseField
|
:param kwargs: all other args to be populated by BaseField
|
||||||
:type kwargs: Any
|
:type kwargs: Any
|
||||||
:return: ormar ForeignKeyField with relation to selected model
|
:return: ormar ForeignKeyField with relation to selected model
|
||||||
:rtype: returns ForeignKeyField
|
:rtype: ForeignKeyField
|
||||||
"""
|
"""
|
||||||
fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname)
|
fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname)
|
||||||
to_field = to.Meta.model_fields[to.Meta.pkname]
|
to_field = to.Meta.model_fields[to.Meta.pkname]
|
||||||
|
|||||||
@ -19,6 +19,29 @@ def ManyToMany(
|
|||||||
virtual: bool = False,
|
virtual: bool = False,
|
||||||
**kwargs: Any
|
**kwargs: Any
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Despite a name it's a function that returns constructed ManyToManyField.
|
||||||
|
This function is actually used in model declaration
|
||||||
|
(as ormar.ManyToMany(ToModel, through=ThroughModel)).
|
||||||
|
|
||||||
|
Accepts number of relation setting parameters as well as all BaseField ones.
|
||||||
|
|
||||||
|
:param to: target related ormar Model
|
||||||
|
:type to: Model class
|
||||||
|
:param through: through model for m2m relation
|
||||||
|
:type through: Model class
|
||||||
|
:param name: name of the database field - later called alias
|
||||||
|
:type name: str
|
||||||
|
:param unique: parameter passed to sqlalchemy.ForeignKey, unique flag
|
||||||
|
:type unique: bool
|
||||||
|
:param virtual: marks if relation is virtual.
|
||||||
|
It is for reversed FK and auto generated FK on through model in Many2Many relations.
|
||||||
|
:type virtual: bool
|
||||||
|
:param kwargs: all other args to be populated by BaseField
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: ormar ManyToManyField with m2m relation to selected model
|
||||||
|
:rtype: ManyToManyField
|
||||||
|
"""
|
||||||
to_field = to.Meta.model_fields[to.Meta.pkname]
|
to_field = to.Meta.model_fields[to.Meta.pkname]
|
||||||
related_name = kwargs.pop("related_name", None)
|
related_name = kwargs.pop("related_name", None)
|
||||||
nullable = kwargs.pop("nullable", True)
|
nullable = kwargs.pop("nullable", True)
|
||||||
@ -49,8 +72,17 @@ def ManyToMany(
|
|||||||
|
|
||||||
|
|
||||||
class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol):
|
class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol):
|
||||||
|
"""
|
||||||
|
Actual class returned from ManyToMany function call and stored in model_fields.
|
||||||
|
"""
|
||||||
|
|
||||||
through: Type["Model"]
|
through: Type["Model"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_target_field_name(cls) -> str:
|
def default_target_field_name(cls) -> str:
|
||||||
|
"""
|
||||||
|
Returns default target model name on through model.
|
||||||
|
:return: name of the field
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
return cls.to.get_name()
|
return cls.to.get_name()
|
||||||
|
|||||||
@ -17,6 +17,20 @@ def is_field_nullable(
|
|||||||
server_default: Any,
|
server_default: Any,
|
||||||
pydantic_only: Optional[bool],
|
pydantic_only: Optional[bool],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the given field should be nullable/ optional based on parameters given.
|
||||||
|
|
||||||
|
:param nullable: flag explicit setting a column as nullable
|
||||||
|
:type nullable: Optional[bool]
|
||||||
|
:param default: value or function to be called as default in python
|
||||||
|
:type default: Any
|
||||||
|
:param server_default: function to be called as default by sql server
|
||||||
|
:type server_default: Any
|
||||||
|
:param pydantic_only: flag if fields should not be included in the sql table
|
||||||
|
:type pydantic_only: Optional[bool]
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
if nullable is None:
|
if nullable is None:
|
||||||
return (
|
return (
|
||||||
default is not None
|
default is not None
|
||||||
@ -27,10 +41,24 @@ def is_field_nullable(
|
|||||||
|
|
||||||
|
|
||||||
def is_auto_primary_key(primary_key: bool, autoincrement: bool) -> bool:
|
def is_auto_primary_key(primary_key: bool, autoincrement: bool) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if field is an autoincrement pk -> if yes it's optional.
|
||||||
|
|
||||||
|
:param primary_key: flag if field is a pk field
|
||||||
|
:type primary_key: bool
|
||||||
|
:param autoincrement: flag if field should be autoincrement
|
||||||
|
:type autoincrement: bool
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return primary_key and autoincrement
|
return primary_key and autoincrement
|
||||||
|
|
||||||
|
|
||||||
class ModelFieldFactory:
|
class ModelFieldFactory:
|
||||||
|
"""
|
||||||
|
Default field factory that construct Field classes and populated their values.
|
||||||
|
"""
|
||||||
|
|
||||||
_bases: Any = (BaseField,)
|
_bases: Any = (BaseField,)
|
||||||
_type: Any = None
|
_type: Any = None
|
||||||
|
|
||||||
@ -66,10 +94,24 @@ class ModelFieldFactory:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_column_type(cls, **kwargs: Any) -> Any: # pragma no cover
|
def get_column_type(cls, **kwargs: Any) -> Any: # pragma no cover
|
||||||
|
"""
|
||||||
|
Return proper type of db column for given field type.
|
||||||
|
Accepts required and optional parameters that each column type accepts.
|
||||||
|
|
||||||
|
:param kwargs: key, value pairs of sqlalchemy options
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: initialized column with proper options
|
||||||
|
:rtype: sqlalchemy Column
|
||||||
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, **kwargs: Any) -> None: # pragma no cover
|
def validate(cls, **kwargs: Any) -> None: # pragma no cover
|
||||||
|
"""
|
||||||
|
Used to validate if all required parameters on a given field type are set.
|
||||||
|
:param kwargs: all params passed during construction
|
||||||
|
:type kwargs: Any
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ class UUID(TypeDecorator): # pragma nocover
|
|||||||
"""
|
"""
|
||||||
Platform-independent GUID type.
|
Platform-independent GUID type.
|
||||||
Uses CHAR(36) if in a string mode, otherwise uses CHAR(32), to store UUID.
|
Uses CHAR(36) if in a string mode, otherwise uses CHAR(32), to store UUID.
|
||||||
|
|
||||||
|
For details for different methods check documentation of parent class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
impl = CHAR
|
impl = CHAR
|
||||||
@ -24,6 +26,14 @@ class UUID(TypeDecorator): # pragma nocover
|
|||||||
return "CHAR(32)"
|
return "CHAR(32)"
|
||||||
|
|
||||||
def _cast_to_uuid(self, value: Union[str, int, bytes]) -> uuid.UUID:
|
def _cast_to_uuid(self, value: Union[str, int, bytes]) -> uuid.UUID:
|
||||||
|
"""
|
||||||
|
Parses given value into uuid.UUID field.
|
||||||
|
|
||||||
|
:param value: value to be parsed
|
||||||
|
:type value: Union[str, int, bytes]
|
||||||
|
:return: initialized uuid
|
||||||
|
:rtype: uuid.UUID
|
||||||
|
"""
|
||||||
if not isinstance(value, uuid.UUID):
|
if not isinstance(value, uuid.UUID):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
ret_value = uuid.UUID(bytes=value)
|
ret_value = uuid.UUID(bytes=value)
|
||||||
|
|||||||
@ -24,6 +24,18 @@ if TYPE_CHECKING: # pragma: no cover
|
|||||||
def add_relation_field_to_fields(
|
def add_relation_field_to_fields(
|
||||||
fields: Union[Set[Any], Dict[Any, Any], None], related_field_name: str
|
fields: Union[Set[Any], Dict[Any, Any], None], related_field_name: str
|
||||||
) -> Union[Set[Any], Dict[Any, Any], None]:
|
) -> Union[Set[Any], Dict[Any, Any], None]:
|
||||||
|
"""
|
||||||
|
Adds related field into fields to include as otherwise it would be skipped.
|
||||||
|
Related field is added only if fields are already populated.
|
||||||
|
Empty fields implies all fields.
|
||||||
|
|
||||||
|
:param fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:type fields: Dict
|
||||||
|
:param related_field_name: name of the field with relation
|
||||||
|
:type related_field_name: str
|
||||||
|
:return: updated fields dict
|
||||||
|
:rtype: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
"""
|
||||||
if fields and related_field_name not in fields:
|
if fields and related_field_name not in fields:
|
||||||
if isinstance(fields, dict):
|
if isinstance(fields, dict):
|
||||||
fields[related_field_name] = ...
|
fields[related_field_name] = ...
|
||||||
@ -33,6 +45,18 @@ def add_relation_field_to_fields(
|
|||||||
|
|
||||||
|
|
||||||
def sort_models(models: List["Model"], orders_by: Dict) -> List["Model"]:
|
def sort_models(models: List["Model"], orders_by: Dict) -> List["Model"]:
|
||||||
|
"""
|
||||||
|
Since prefetch query gets all related models by ids the sorting needs to happen in
|
||||||
|
python. Since by default models are already sorted by id here we resort only if
|
||||||
|
order_by parameters was set.
|
||||||
|
|
||||||
|
:param models: list of models already fetched from db
|
||||||
|
:type models: List[tests.test_prefetch_related.Division]
|
||||||
|
:param orders_by: order by dictionary
|
||||||
|
:type orders_by: Dict[str, str]
|
||||||
|
:return: sorted list of models
|
||||||
|
:rtype: List[tests.test_prefetch_related.Division]
|
||||||
|
"""
|
||||||
sort_criteria = [
|
sort_criteria = [
|
||||||
(key, value) for key, value in orders_by.items() if isinstance(value, str)
|
(key, value) for key, value in orders_by.items() if isinstance(value, str)
|
||||||
]
|
]
|
||||||
@ -54,6 +78,29 @@ def set_children_on_model( # noqa: CCR001
|
|||||||
models: Dict,
|
models: Dict,
|
||||||
orders_by: Dict,
|
orders_by: Dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Extract ids of child models by given relation id key value.
|
||||||
|
|
||||||
|
Based on those ids the actual children model instances are fetched from
|
||||||
|
already fetched data.
|
||||||
|
|
||||||
|
If needed the child models are resorted according to passed orders_by dict.
|
||||||
|
|
||||||
|
Also relation is registered as each child is set as parent related field name value.
|
||||||
|
|
||||||
|
:param model: parent model instance
|
||||||
|
:type model: Model
|
||||||
|
:param related: name of the related field
|
||||||
|
:type related: str
|
||||||
|
:param children: dictionary of children ids/ related field value
|
||||||
|
:type children: Dict[int, set]
|
||||||
|
:param model_id: id of the model on which children should be set
|
||||||
|
:type model_id: int
|
||||||
|
:param models: dictionary of child models instances
|
||||||
|
:type models: Dict
|
||||||
|
:param orders_by: order_by dictionary
|
||||||
|
:type orders_by: Dict
|
||||||
|
"""
|
||||||
for key, child_models in children.items():
|
for key, child_models in children.items():
|
||||||
if key == model_id:
|
if key == model_id:
|
||||||
models_to_set = [models[child] for child in sorted(child_models)]
|
models_to_set = [models[child] for child in sorted(child_models)]
|
||||||
@ -67,6 +114,12 @@ def set_children_on_model( # noqa: CCR001
|
|||||||
|
|
||||||
|
|
||||||
class PrefetchQuery:
|
class PrefetchQuery:
|
||||||
|
"""
|
||||||
|
Query used to fetch related models in subsequent queries.
|
||||||
|
Each model is fetched only ones by the name of the relation.
|
||||||
|
That means that for each prefetch_related entry next query is issued to database.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__( # noqa: CFQ002
|
def __init__( # noqa: CFQ002
|
||||||
self,
|
self,
|
||||||
model_cls: Type["Model"],
|
model_cls: Type["Model"],
|
||||||
@ -92,6 +145,22 @@ class PrefetchQuery:
|
|||||||
async def prefetch_related(
|
async def prefetch_related(
|
||||||
self, models: Sequence["Model"], rows: List
|
self, models: Sequence["Model"], rows: List
|
||||||
) -> Sequence["Model"]:
|
) -> Sequence["Model"]:
|
||||||
|
"""
|
||||||
|
Main entry point for prefetch_query.
|
||||||
|
|
||||||
|
Receives list of already initialized parent models with all children from
|
||||||
|
select_related already populated. Receives also list of row sql result rows
|
||||||
|
as it's quicker to extract ids that way instead of calling each model.
|
||||||
|
|
||||||
|
Returns list with related models already prefetched and set.
|
||||||
|
|
||||||
|
:param models: list of already instantiated models from main query
|
||||||
|
:type models: List[Model]
|
||||||
|
:param rows: row sql result of the main query before the prefetch
|
||||||
|
:type rows: List[sqlalchemy.engine.result.RowProxy]
|
||||||
|
:return: list of models with children prefetched
|
||||||
|
:rtype: List[Model]
|
||||||
|
"""
|
||||||
self.models = extract_models_to_dict_of_lists(
|
self.models = extract_models_to_dict_of_lists(
|
||||||
model_type=self.model, models=models, select_dict=self.select_dict
|
model_type=self.model, models=models, select_dict=self.select_dict
|
||||||
)
|
)
|
||||||
@ -101,6 +170,17 @@ class PrefetchQuery:
|
|||||||
def _extract_ids_from_raw_data(
|
def _extract_ids_from_raw_data(
|
||||||
self, parent_model: Type["Model"], column_name: str
|
self, parent_model: Type["Model"], column_name: str
|
||||||
) -> Set:
|
) -> Set:
|
||||||
|
"""
|
||||||
|
Iterates over raw rows and extract id values of relation columns by using
|
||||||
|
prefixed column name.
|
||||||
|
|
||||||
|
:param parent_model: ormar model class
|
||||||
|
:type parent_model: Type[Model]
|
||||||
|
:param column_name: name of the relation column which is a key column
|
||||||
|
:type column_name: str
|
||||||
|
:return: set of ids of related model that should be extracted
|
||||||
|
:rtype: set
|
||||||
|
"""
|
||||||
list_of_ids = set()
|
list_of_ids = set()
|
||||||
current_data = self.already_extracted.get(parent_model.get_name(), {})
|
current_data = self.already_extracted.get(parent_model.get_name(), {})
|
||||||
table_prefix = current_data.get("prefix", "")
|
table_prefix = current_data.get("prefix", "")
|
||||||
@ -113,6 +193,17 @@ class PrefetchQuery:
|
|||||||
def _extract_ids_from_preloaded_models(
|
def _extract_ids_from_preloaded_models(
|
||||||
self, parent_model: Type["Model"], column_name: str
|
self, parent_model: Type["Model"], column_name: str
|
||||||
) -> Set:
|
) -> Set:
|
||||||
|
"""
|
||||||
|
Extracts relation ids from already populated models if they were included
|
||||||
|
in the original query before.
|
||||||
|
|
||||||
|
:param parent_model: model from which related ids should be extracted
|
||||||
|
:type parent_model: Type["Model"]
|
||||||
|
:param column_name: name of the relation column which is a key column
|
||||||
|
:type column_name: str
|
||||||
|
:return: set of ids of related model that should be extracted
|
||||||
|
:rtype: set
|
||||||
|
"""
|
||||||
list_of_ids = set()
|
list_of_ids = set()
|
||||||
for model in self.models.get(parent_model.get_name(), []):
|
for model in self.models.get(parent_model.get_name(), []):
|
||||||
child = getattr(model, column_name)
|
child = getattr(model, column_name)
|
||||||
@ -125,7 +216,19 @@ class PrefetchQuery:
|
|||||||
def _extract_required_ids(
|
def _extract_required_ids(
|
||||||
self, parent_model: Type["Model"], reverse: bool, related: str,
|
self, parent_model: Type["Model"], reverse: bool, related: str,
|
||||||
) -> Set:
|
) -> Set:
|
||||||
|
"""
|
||||||
|
Delegates extraction of the fields to either get ids from raw sql response
|
||||||
|
or from already populated models.
|
||||||
|
|
||||||
|
:param parent_model: model from which related ids should be extracted
|
||||||
|
:type parent_model: Type["Model"]
|
||||||
|
:param reverse: flag if the relation is reverse
|
||||||
|
:type reverse: bool
|
||||||
|
:param related: name of the field with relation
|
||||||
|
:type related: str
|
||||||
|
:return: set of ids of related model that should be extracted
|
||||||
|
:rtype: set
|
||||||
|
"""
|
||||||
use_raw = parent_model.get_name() not in self.models
|
use_raw = parent_model.get_name() not in self.models
|
||||||
|
|
||||||
column_name = parent_model.get_column_name_for_id_extraction(
|
column_name = parent_model.get_column_name_for_id_extraction(
|
||||||
@ -151,6 +254,23 @@ class PrefetchQuery:
|
|||||||
reverse: bool,
|
reverse: bool,
|
||||||
related: str,
|
related: str,
|
||||||
) -> List:
|
) -> List:
|
||||||
|
"""
|
||||||
|
Populates where clause with condition to return only models within the
|
||||||
|
set of extracted ids.
|
||||||
|
|
||||||
|
If there are no ids for relation the empty list is returned.
|
||||||
|
|
||||||
|
:param parent_model: model from which related ids should be extracted
|
||||||
|
:type parent_model: Type["Model"]
|
||||||
|
:param target_model: model to which relation leads to
|
||||||
|
:type target_model: Type["Model"]
|
||||||
|
:param reverse: flag if the relation is reverse
|
||||||
|
:type reverse: bool
|
||||||
|
:param related: name of the field with relation
|
||||||
|
:type related: str
|
||||||
|
:return:
|
||||||
|
:rtype: List[sqlalchemy.sql.elements.TextClause]
|
||||||
|
"""
|
||||||
ids = self._extract_required_ids(
|
ids = self._extract_required_ids(
|
||||||
parent_model=parent_model, reverse=reverse, related=related
|
parent_model=parent_model, reverse=reverse, related=related
|
||||||
)
|
)
|
||||||
@ -175,7 +295,19 @@ class PrefetchQuery:
|
|||||||
def _populate_nested_related(
|
def _populate_nested_related(
|
||||||
self, model: "Model", prefetch_dict: Dict, orders_by: Dict,
|
self, model: "Model", prefetch_dict: Dict, orders_by: Dict,
|
||||||
) -> "Model":
|
) -> "Model":
|
||||||
|
"""
|
||||||
|
Populates all related models children of parent model that are
|
||||||
|
included in prefetch query.
|
||||||
|
|
||||||
|
:param model: ormar model instance
|
||||||
|
:type model: Model
|
||||||
|
:param prefetch_dict: dictionary of models to prefetch
|
||||||
|
:type prefetch_dict: Dict
|
||||||
|
:param orders_by: dictionary of order bys
|
||||||
|
:type orders_by: Dict
|
||||||
|
:return: model with children populated
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
related_to_extract = model.get_filtered_names_to_extract(
|
related_to_extract = model.get_filtered_names_to_extract(
|
||||||
prefetch_dict=prefetch_dict
|
prefetch_dict=prefetch_dict
|
||||||
)
|
)
|
||||||
@ -206,6 +338,24 @@ class PrefetchQuery:
|
|||||||
async def _prefetch_related_models(
|
async def _prefetch_related_models(
|
||||||
self, models: Sequence["Model"], rows: List
|
self, models: Sequence["Model"], rows: List
|
||||||
) -> Sequence["Model"]:
|
) -> Sequence["Model"]:
|
||||||
|
"""
|
||||||
|
Main method of the query.
|
||||||
|
|
||||||
|
Translates select nad prefetch list into dictionaries to avoid querying the
|
||||||
|
same related models multiple times.
|
||||||
|
|
||||||
|
Keeps the list of already extracted models.
|
||||||
|
|
||||||
|
Extracts the related models from the database and later populate all children
|
||||||
|
on each of the parent models from list.
|
||||||
|
|
||||||
|
:param models: list of parent models from main query
|
||||||
|
:type models: List[Model]
|
||||||
|
:param rows: raw response from sql query
|
||||||
|
:type rows: List[sqlalchemy.engine.result.RowProxy]
|
||||||
|
:return: list of models with prefetch children populated
|
||||||
|
:rtype: List[Model]
|
||||||
|
"""
|
||||||
self.already_extracted = {self.model.get_name(): {"raw": rows}}
|
self.already_extracted = {self.model.get_name(): {"raw": rows}}
|
||||||
select_dict = translate_list_to_dict(self._select_related)
|
select_dict = translate_list_to_dict(self._select_related)
|
||||||
prefetch_dict = translate_list_to_dict(self._prefetch_related)
|
prefetch_dict = translate_list_to_dict(self._prefetch_related)
|
||||||
@ -242,7 +392,32 @@ class PrefetchQuery:
|
|||||||
exclude_fields: Union[Set[Any], Dict[Any, Any], None],
|
exclude_fields: Union[Set[Any], Dict[Any, Any], None],
|
||||||
orders_by: Dict,
|
orders_by: Dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Constructs queries with required ids and extracts data with fields that should
|
||||||
|
be included/excluded.
|
||||||
|
|
||||||
|
Runs the queries against the database and populated dictionaries with ids and
|
||||||
|
with actual extracted children models.
|
||||||
|
|
||||||
|
Calls itself recurrently to extract deeper nested relations of related model.
|
||||||
|
|
||||||
|
:param related: name of the relation
|
||||||
|
:type related: str
|
||||||
|
:param target_model: model to which relation leads to
|
||||||
|
:type target_model: Type[Model]
|
||||||
|
:param prefetch_dict: prefetch related list converted into dictionary
|
||||||
|
:type prefetch_dict: Dict
|
||||||
|
:param select_dict: select related list converted into dictionary
|
||||||
|
:type select_dict: Dict
|
||||||
|
:param fields: fields to include
|
||||||
|
:type fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param exclude_fields: fields to exclude
|
||||||
|
:type exclude_fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param orders_by: dictionary of order bys clauses
|
||||||
|
:type orders_by: Dict
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
fields = target_model.get_included(fields, related)
|
fields = target_model.get_included(fields, related)
|
||||||
exclude_fields = target_model.get_excluded(exclude_fields, related)
|
exclude_fields = target_model.get_excluded(exclude_fields, related)
|
||||||
target_field = target_model.Meta.model_fields[related]
|
target_field = target_model.Meta.model_fields[related]
|
||||||
@ -320,6 +495,24 @@ class PrefetchQuery:
|
|||||||
exclude_fields: Union[Set[Any], Dict[Any, Any], None],
|
exclude_fields: Union[Set[Any], Dict[Any, Any], None],
|
||||||
filter_clauses: List,
|
filter_clauses: List,
|
||||||
) -> Tuple[str, List]:
|
) -> Tuple[str, List]:
|
||||||
|
"""
|
||||||
|
Actually runs the queries against the database and populates the raw response
|
||||||
|
for given related model.
|
||||||
|
|
||||||
|
Returns table prefix as it's later needed to eventually initialize the children
|
||||||
|
models.
|
||||||
|
|
||||||
|
:param target_field: ormar field with relation definition
|
||||||
|
:type target_field: Type["BaseField"]
|
||||||
|
:param fields: fields to include
|
||||||
|
:type fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param exclude_fields: fields to exclude
|
||||||
|
:type exclude_fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param filter_clauses: list of clauses, actually one clause with ids of relation
|
||||||
|
:type filter_clauses: List[sqlalchemy.sql.elements.TextClause]
|
||||||
|
:return: table prefix and raw rows from sql response
|
||||||
|
:rtype: Tuple[str, List]
|
||||||
|
"""
|
||||||
target_model = target_field.to
|
target_model = target_field.to
|
||||||
target_name = target_model.get_name()
|
target_name = target_model.get_name()
|
||||||
select_related = []
|
select_related = []
|
||||||
@ -353,6 +546,17 @@ class PrefetchQuery:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_select_related_if_apply(related: str, select_dict: Dict) -> Dict:
|
def _get_select_related_if_apply(related: str, select_dict: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
Extract nested part of select_related dictionary to extract models nested
|
||||||
|
deeper on related model and already loaded in select related query.
|
||||||
|
|
||||||
|
:param related: name of the relation
|
||||||
|
:type related: str
|
||||||
|
:param select_dict: dictionary of select related models in main query
|
||||||
|
:type select_dict: Dict
|
||||||
|
:return: dictionary with nested part of select related
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
return (
|
return (
|
||||||
select_dict.get(related, {})
|
select_dict.get(related, {})
|
||||||
if (select_dict and select_dict is not Ellipsis and related in select_dict)
|
if (select_dict and select_dict is not Ellipsis and related in select_dict)
|
||||||
@ -362,6 +566,16 @@ class PrefetchQuery:
|
|||||||
def _update_already_loaded_rows( # noqa: CFQ002
|
def _update_already_loaded_rows( # noqa: CFQ002
|
||||||
self, target_field: Type["BaseField"], prefetch_dict: Dict, orders_by: Dict,
|
self, target_field: Type["BaseField"], prefetch_dict: Dict, orders_by: Dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Updates models that are already loaded, usually children of children.
|
||||||
|
|
||||||
|
:param target_field: ormar field with relation definition
|
||||||
|
:type target_field: Type["BaseField"]
|
||||||
|
:param prefetch_dict: dictionaries of related models to prefetch
|
||||||
|
:type prefetch_dict: Dict
|
||||||
|
:param orders_by: dictionary of order by clauses by model
|
||||||
|
:type orders_by: Dict
|
||||||
|
"""
|
||||||
target_model = target_field.to
|
target_model = target_field.to
|
||||||
for instance in self.models.get(target_model.get_name(), []):
|
for instance in self.models.get(target_model.get_name(), []):
|
||||||
self._populate_nested_related(
|
self._populate_nested_related(
|
||||||
@ -379,6 +593,33 @@ class PrefetchQuery:
|
|||||||
prefetch_dict: Dict,
|
prefetch_dict: Dict,
|
||||||
orders_by: Dict,
|
orders_by: Dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Instantiates children models extracted from given relation.
|
||||||
|
|
||||||
|
Populates them with their own nested children if they are included in prefetch
|
||||||
|
query.
|
||||||
|
|
||||||
|
Sets the initialized models and ids of them under corresponding keys in
|
||||||
|
already_extracted dictionary. Later those instances will be fetched by ids
|
||||||
|
and set on the parent model after sorting if needed.
|
||||||
|
|
||||||
|
:param rows: raw sql response from the prefetch query
|
||||||
|
:type rows: List[sqlalchemy.engine.result.RowProxy]
|
||||||
|
:param target_field: field with relation definition from parent model
|
||||||
|
:type target_field: Type["BaseField"]
|
||||||
|
:param parent_model: model with relation definition
|
||||||
|
:type parent_model: Type[Model]
|
||||||
|
:param table_prefix: prefix of the target table from current relation
|
||||||
|
:type table_prefix: str
|
||||||
|
:param fields: fields to include
|
||||||
|
:type fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param exclude_fields: fields to exclude
|
||||||
|
:type exclude_fields: Union[Set[Any], Dict[Any, Any], None]
|
||||||
|
:param prefetch_dict: dictionaries of related models to prefetch
|
||||||
|
:type prefetch_dict: Dict
|
||||||
|
:param orders_by: dictionary of order by clauses by model
|
||||||
|
:type orders_by: Dict
|
||||||
|
"""
|
||||||
target_model = target_field.to
|
target_model = target_field.to
|
||||||
for row in rows:
|
for row in rows:
|
||||||
field_name = parent_model.get_related_field_name(target_field=target_field)
|
field_name = parent_model.get_related_field_name(target_field=target_field)
|
||||||
|
|||||||
@ -49,19 +49,41 @@ class Query:
|
|||||||
self.limit_raw_sql = limit_raw_sql
|
self.limit_raw_sql = limit_raw_sql
|
||||||
|
|
||||||
def _init_sorted_orders(self) -> None:
|
def _init_sorted_orders(self) -> None:
|
||||||
|
"""
|
||||||
|
Initialize empty order_by dict to be populated later during the query call
|
||||||
|
"""
|
||||||
if self.order_columns:
|
if self.order_columns:
|
||||||
for clause in self.order_columns:
|
for clause in self.order_columns:
|
||||||
self.sorted_orders[clause] = None
|
self.sorted_orders[clause] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prefixed_pk_name(self) -> str:
|
def prefixed_pk_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Shortcut for extracting prefixed with alias primary key column name from main
|
||||||
|
model
|
||||||
|
:return: alias of pk column prefix with table name.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
pkname_alias = self.model_cls.get_column_alias(self.model_cls.Meta.pkname)
|
pkname_alias = self.model_cls.get_column_alias(self.model_cls.Meta.pkname)
|
||||||
return f"{self.table.name}.{pkname_alias}"
|
return f"{self.table.name}.{pkname_alias}"
|
||||||
|
|
||||||
def alias(self, name: str) -> str:
|
def alias(self, name: str) -> str:
|
||||||
|
"""
|
||||||
|
Shortcut to extracting column alias from given master model.
|
||||||
|
|
||||||
|
:param name: name of column
|
||||||
|
:type name: str
|
||||||
|
:return: alias of given column name
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
return self.model_cls.get_column_alias(name)
|
return self.model_cls.get_column_alias(name)
|
||||||
|
|
||||||
def apply_order_bys_for_primary_model(self) -> None: # noqa: CCR001
|
def apply_order_bys_for_primary_model(self) -> None: # noqa: CCR001
|
||||||
|
"""
|
||||||
|
Applies order_by queries on main model when it's used as a subquery.
|
||||||
|
That way the subquery with limit and offset only on main model has proper
|
||||||
|
sorting applied and correct models are fetched.
|
||||||
|
"""
|
||||||
if self.order_columns:
|
if self.order_columns:
|
||||||
for clause in self.order_columns:
|
for clause in self.order_columns:
|
||||||
if "__" not in clause:
|
if "__" not in clause:
|
||||||
@ -91,6 +113,18 @@ class Query:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def build_select_expression(self) -> Tuple[sqlalchemy.sql.select, List[str]]:
|
def build_select_expression(self) -> Tuple[sqlalchemy.sql.select, List[str]]:
|
||||||
|
"""
|
||||||
|
Main entry point from outside (after proper initialization).
|
||||||
|
|
||||||
|
Extracts columns list to fetch,
|
||||||
|
construct all required joins for select related,
|
||||||
|
then applies all conditional and sort clauses.
|
||||||
|
|
||||||
|
Returns ready to run query with all joins and clauses.
|
||||||
|
|
||||||
|
:return: ready to run query with all joins and clauses.
|
||||||
|
:rtype: sqlalchemy.sql.selectable.Select
|
||||||
|
"""
|
||||||
self_related_fields = self.model_cls.own_table_columns(
|
self_related_fields = self.model_cls.own_table_columns(
|
||||||
model=self.model_cls,
|
model=self.model_cls,
|
||||||
fields=self.fields,
|
fields=self.fields,
|
||||||
@ -184,6 +218,21 @@ class Query:
|
|||||||
def _apply_expression_modifiers(
|
def _apply_expression_modifiers(
|
||||||
self, expr: sqlalchemy.sql.select
|
self, expr: sqlalchemy.sql.select
|
||||||
) -> sqlalchemy.sql.select:
|
) -> sqlalchemy.sql.select:
|
||||||
|
"""
|
||||||
|
Receives the select query (might be join) and applies:
|
||||||
|
* Filter clauses
|
||||||
|
* Exclude filter clauses
|
||||||
|
* Limit clauses
|
||||||
|
* Offset clauses
|
||||||
|
* Order by clauses
|
||||||
|
|
||||||
|
Returns complete ready to run query.
|
||||||
|
|
||||||
|
:param expr: select expression before clauses
|
||||||
|
:type expr: sqlalchemy.sql.selectable.Select
|
||||||
|
:return: expresion with all present clauses applied
|
||||||
|
:rtype: sqlalchemy.sql.selectable.Select
|
||||||
|
"""
|
||||||
expr = FilterQuery(filter_clauses=self.filter_clauses).apply(expr)
|
expr = FilterQuery(filter_clauses=self.filter_clauses).apply(expr)
|
||||||
expr = FilterQuery(filter_clauses=self.exclude_clauses, exclude=True).apply(
|
expr = FilterQuery(filter_clauses=self.exclude_clauses, exclude=True).apply(
|
||||||
expr
|
expr
|
||||||
@ -195,6 +244,10 @@ class Query:
|
|||||||
return expr
|
return expr
|
||||||
|
|
||||||
def _reset_query_parameters(self) -> None:
|
def _reset_query_parameters(self) -> None:
|
||||||
|
"""
|
||||||
|
Although it should be created each time before the call we reset the key params
|
||||||
|
anyway.
|
||||||
|
"""
|
||||||
self.select_from = []
|
self.select_from = []
|
||||||
self.columns = []
|
self.columns = []
|
||||||
self.used_aliases = []
|
self.used_aliases = []
|
||||||
|
|||||||
@ -18,6 +18,22 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
def check_node_not_dict_or_not_last_node(
|
def check_node_not_dict_or_not_last_node(
|
||||||
part: str, parts: List, current_level: Any
|
part: str, parts: List, current_level: Any
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if given name is not present in the current level of the structure.
|
||||||
|
Checks if given name is not the last name in the split list of parts.
|
||||||
|
Checks if the given name in current level is not a dictionary.
|
||||||
|
|
||||||
|
All those checks verify if there is a need for deeper traversal.
|
||||||
|
|
||||||
|
:param part:
|
||||||
|
:type part: str
|
||||||
|
:param parts:
|
||||||
|
:type parts: List[str]
|
||||||
|
:param current_level: current level of the traversed structure
|
||||||
|
:type current_level: Any
|
||||||
|
:return: result of the check
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return (part not in current_level and part != parts[-1]) or (
|
return (part not in current_level and part != parts[-1]) or (
|
||||||
part in current_level and not isinstance(current_level[part], dict)
|
part in current_level and not isinstance(current_level[part], dict)
|
||||||
)
|
)
|
||||||
@ -26,6 +42,21 @@ 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], is_order: bool = False
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Splits the list of strings by '__' and converts them to dictionary with nested
|
||||||
|
models grouped by parent model. That way each model appears only once in the whole
|
||||||
|
dictionary and children are grouped under parent name.
|
||||||
|
|
||||||
|
Default required key ise Ellipsis like in pydantic.
|
||||||
|
|
||||||
|
:param list_to_trans: input list
|
||||||
|
:type list_to_trans: set
|
||||||
|
:param is_order: flag if change affects order_by clauses are they require special
|
||||||
|
default value with sort order.
|
||||||
|
:type is_order: bool
|
||||||
|
:return: converted to dictionary input list
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
new_dict: Dict = dict()
|
new_dict: Dict = dict()
|
||||||
for path in list_to_trans:
|
for path in list_to_trans:
|
||||||
current_level = new_dict
|
current_level = new_dict
|
||||||
@ -50,6 +81,15 @@ def translate_list_to_dict( # noqa: CCR001
|
|||||||
|
|
||||||
|
|
||||||
def convert_set_to_required_dict(set_to_convert: set) -> Dict:
|
def convert_set_to_required_dict(set_to_convert: set) -> Dict:
|
||||||
|
"""
|
||||||
|
Converts set to dictionary of required keys.
|
||||||
|
Required key is Ellipsis.
|
||||||
|
|
||||||
|
:param set_to_convert: set to convert to dict
|
||||||
|
:type set_to_convert: set
|
||||||
|
:return: set converted to dict of ellipsis
|
||||||
|
:rtype: Dict[str, ellipsis]
|
||||||
|
"""
|
||||||
new_dict = dict()
|
new_dict = dict()
|
||||||
for key in set_to_convert:
|
for key in set_to_convert:
|
||||||
new_dict[key] = Ellipsis
|
new_dict[key] = Ellipsis
|
||||||
@ -57,6 +97,19 @@ def convert_set_to_required_dict(set_to_convert: set) -> Dict:
|
|||||||
|
|
||||||
|
|
||||||
def update(current_dict: Any, updating_dict: Any) -> Dict: # noqa: CCR001
|
def update(current_dict: Any, updating_dict: Any) -> Dict: # noqa: CCR001
|
||||||
|
"""
|
||||||
|
Update one dict with another but with regard for nested keys.
|
||||||
|
|
||||||
|
That way nested sets are unionised, dicts updated and
|
||||||
|
only other values are overwritten.
|
||||||
|
|
||||||
|
:param current_dict: dict to update
|
||||||
|
:type current_dict: Dict[str, ellipsis]
|
||||||
|
:param updating_dict: dict with values to update
|
||||||
|
:type updating_dict: Dict
|
||||||
|
:return: combination of both dicts
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
if current_dict is Ellipsis:
|
if current_dict is Ellipsis:
|
||||||
current_dict = dict()
|
current_dict = dict()
|
||||||
for key, value in updating_dict.items():
|
for key, value in updating_dict.items():
|
||||||
@ -73,6 +126,17 @@ def update(current_dict: Any, updating_dict: Any) -> Dict: # noqa: CCR001
|
|||||||
|
|
||||||
|
|
||||||
def update_dict_from_list(curr_dict: Dict, list_to_update: Union[List, Set]) -> Dict:
|
def update_dict_from_list(curr_dict: Dict, list_to_update: Union[List, Set]) -> Dict:
|
||||||
|
"""
|
||||||
|
Converts the list into dictionary and later performs special update, where
|
||||||
|
nested keys that are sets or dicts are combined and not overwritten.
|
||||||
|
|
||||||
|
:param curr_dict: dict to update
|
||||||
|
:type curr_dict: Dict
|
||||||
|
:param list_to_update: list with values to update the dict
|
||||||
|
:type list_to_update: List[str]
|
||||||
|
:return: updated dict
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
updated_dict = copy.copy(curr_dict)
|
updated_dict = copy.copy(curr_dict)
|
||||||
dict_to_update = translate_list_to_dict(list_to_update)
|
dict_to_update = translate_list_to_dict(list_to_update)
|
||||||
update(updated_dict, dict_to_update)
|
update(updated_dict, dict_to_update)
|
||||||
@ -82,6 +146,25 @@ def update_dict_from_list(curr_dict: Dict, list_to_update: Union[List, Set]) ->
|
|||||||
def extract_nested_models( # noqa: CCR001
|
def extract_nested_models( # noqa: CCR001
|
||||||
model: "Model", model_type: Type["Model"], select_dict: Dict, extracted: Dict
|
model: "Model", model_type: Type["Model"], select_dict: Dict, extracted: Dict
|
||||||
) -> None:
|
) -> 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]
|
follow = [rel for rel in model_type.extract_related_names() if rel in select_dict]
|
||||||
for related in follow:
|
for related in follow:
|
||||||
child = getattr(model, related)
|
child = getattr(model, related)
|
||||||
@ -108,6 +191,22 @@ def extract_models_to_dict_of_lists(
|
|||||||
select_dict: Dict,
|
select_dict: Dict,
|
||||||
extracted: Dict = None,
|
extracted: Dict = None,
|
||||||
) -> Dict:
|
) -> 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:
|
if not extracted:
|
||||||
extracted = dict()
|
extracted = dict()
|
||||||
for model in models:
|
for model in models:
|
||||||
|
|||||||
@ -9,6 +9,14 @@ if TYPE_CHECKING: # pragma: no cover
|
|||||||
|
|
||||||
|
|
||||||
def callable_accepts_kwargs(func: Callable) -> bool:
|
def callable_accepts_kwargs(func: Callable) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if function accepts **kwargs.
|
||||||
|
|
||||||
|
:param func: function which signature needs to be checked
|
||||||
|
:type func: function
|
||||||
|
:return:
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
return any(
|
return any(
|
||||||
p
|
p
|
||||||
for p in inspect.signature(func).parameters.values()
|
for p in inspect.signature(func).parameters.values()
|
||||||
@ -17,16 +25,37 @@ def callable_accepts_kwargs(func: Callable) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def make_id(target: Any) -> Union[int, Tuple[int, int]]:
|
def make_id(target: Any) -> Union[int, Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Creates id of a function or method to be used as key to store signal
|
||||||
|
|
||||||
|
:param target: target which id we want
|
||||||
|
:type target: Any
|
||||||
|
:return: id of the target
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
if hasattr(target, "__func__"):
|
if hasattr(target, "__func__"):
|
||||||
return id(target.__self__), id(target.__func__)
|
return id(target.__self__), id(target.__func__)
|
||||||
return id(target)
|
return id(target)
|
||||||
|
|
||||||
|
|
||||||
class Signal:
|
class Signal:
|
||||||
|
"""
|
||||||
|
Signal that notifies all receiver functions.
|
||||||
|
In ormar used by models to send pre_save, post_save etc. signals.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._receivers: List[Tuple[Union[int, Tuple[int, int]], Callable]] = []
|
self._receivers: List[Tuple[Union[int, Tuple[int, int]], Callable]] = []
|
||||||
|
|
||||||
def connect(self, receiver: Callable) -> None:
|
def connect(self, receiver: Callable) -> None:
|
||||||
|
"""
|
||||||
|
Connects given receiver function to the signal.
|
||||||
|
|
||||||
|
:raises: SignalDefinitionError if receiver is not callable
|
||||||
|
or not accept **kwargs
|
||||||
|
:param receiver: receiver function
|
||||||
|
:type receiver: Callable
|
||||||
|
"""
|
||||||
if not callable(receiver):
|
if not callable(receiver):
|
||||||
raise SignalDefinitionError("Signal receivers must be callable.")
|
raise SignalDefinitionError("Signal receivers must be callable.")
|
||||||
if not callable_accepts_kwargs(receiver):
|
if not callable_accepts_kwargs(receiver):
|
||||||
@ -38,6 +67,14 @@ class Signal:
|
|||||||
self._receivers.append((new_receiver_key, receiver))
|
self._receivers.append((new_receiver_key, receiver))
|
||||||
|
|
||||||
def disconnect(self, receiver: Callable) -> bool:
|
def disconnect(self, receiver: Callable) -> bool:
|
||||||
|
"""
|
||||||
|
Removes the receiver function from the signal.
|
||||||
|
|
||||||
|
:param receiver: receiver function
|
||||||
|
:type receiver: Callable
|
||||||
|
:return: flag if receiver was removed
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
removed = False
|
removed = False
|
||||||
new_receiver_key = make_id(receiver)
|
new_receiver_key = make_id(receiver)
|
||||||
for ind, rec in enumerate(self._receivers):
|
for ind, rec in enumerate(self._receivers):
|
||||||
@ -49,6 +86,13 @@ class Signal:
|
|||||||
return removed
|
return removed
|
||||||
|
|
||||||
async def send(self, sender: Type["Model"], **kwargs: Any) -> None:
|
async def send(self, sender: Type["Model"], **kwargs: Any) -> None:
|
||||||
|
"""
|
||||||
|
Notifies all receiver functions with given kwargs
|
||||||
|
:param sender: model that sends the signal
|
||||||
|
:type sender: Type["Model"]
|
||||||
|
:param kwargs: arguments passed to receivers
|
||||||
|
:type kwargs: Any
|
||||||
|
"""
|
||||||
receivers = []
|
receivers = []
|
||||||
for receiver in self._receivers:
|
for receiver in self._receivers:
|
||||||
_, receiver_func = receiver
|
_, receiver_func = receiver
|
||||||
@ -57,6 +101,11 @@ class Signal:
|
|||||||
|
|
||||||
|
|
||||||
class SignalEmitter:
|
class SignalEmitter:
|
||||||
|
"""
|
||||||
|
Emitter that registers the signals in internal dictionary.
|
||||||
|
If signal with given name does not exist it's auto added on access.
|
||||||
|
"""
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
signals: Dict[str, Signal]
|
signals: Dict[str, Signal]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user