add tests for cross model forward references, add docs for processing forwardrefs, wip on refactoring queries into separate pages based on functionality

This commit is contained in:
collerek
2021-01-26 17:29:40 +01:00
parent a2834666fc
commit b710ed9780
39 changed files with 2054 additions and 1004 deletions

View File

@ -217,7 +217,7 @@ primary_key, index, unique, nullable, default and server_default.
```python
| @classmethod
| expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True, relation_name: str = None) -> Any
| expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Any
```
Function overwritten for relations, in basic field the value is returned as is.
@ -236,3 +236,66 @@ dict (from Model) or actual instance/list of a "Model".
`(Any)`: returns untouched value for normal fields, expands only for relations
<a name="fields.base.BaseField.set_self_reference_flag"></a>
#### set\_self\_reference\_flag
```python
| @classmethod
| set_self_reference_flag(cls) -> None
```
Sets `self_reference` to True if field to and owner are same model.
**Returns**:
`(None)`: None
<a name="fields.base.BaseField.has_unresolved_forward_refs"></a>
#### has\_unresolved\_forward\_refs
```python
| @classmethod
| has_unresolved_forward_refs(cls) -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
model can be used.
**Returns**:
`(bool)`: result of the check
<a name="fields.base.BaseField.evaluate_forward_ref"></a>
#### evaluate\_forward\_ref
```python
| @classmethod
| evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
**Arguments**:
- `globalns (Any)`: global namespace
- `localns (Any)`: local namespace
**Returns**:
`(None)`: None
<a name="fields.base.BaseField.get_related_name"></a>
#### get\_related\_name
```python
| @classmethod
| get_related_name(cls) -> str
```
Returns name to use for reverse relation.
It's either set as `related_name` or by default it's owner model. get_name + 's'
**Returns**:
`(str)`: name of the related_name or default related name.

View File

@ -46,6 +46,29 @@ Populates only pk field and set it to desired type.
`(pydantic.BaseModel)`: constructed dummy model
<a name="fields.foreign_key.populate_fk_params_based_on_to_model"></a>
#### populate\_fk\_params\_based\_on\_to\_model
```python
populate_fk_params_based_on_to_model(to: Type["Model"], nullable: bool, onupdate: str = None, ondelete: str = None) -> Tuple[Any, List, Any]
```
Based on target to model to which relation leads to populates the type of the
pydantic field to use, ForeignKey constraint and type of the target column field.
**Arguments**:
- `to (Model class)`: target related ormar Model
- `nullable (bool)`: marks field as optional/ required
- `onupdate (str)`: parameter passed to sqlalchemy.ForeignKey.
How to treat child rows on update of parent (the one where FK is defined) model.
- `ondelete (str)`: parameter passed to sqlalchemy.ForeignKey.
How to treat child rows on delete of parent (the one where FK is defined) model.
**Returns**:
`(Tuple[Any, List, Any])`: tuple with target pydantic type, list of fk constraints and target col type
<a name="fields.foreign_key.UniqueColumns"></a>
## UniqueColumns Objects
@ -71,7 +94,7 @@ to produce sqlalchemy.ForeignKeys
#### ForeignKey
```python
ForeignKey(to: Type["Model"], *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: str = None, ondelete: str = None, **kwargs: Any, ,) -> Any
ForeignKey(to: Union[Type["Model"], "ForwardRef"], *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: str = None, ondelete: str = None, **kwargs: Any, ,) -> Any
```
Despite a name it's a function that returns constructed ForeignKeyField.
@ -107,12 +130,62 @@ class ForeignKeyField(BaseField)
Actual class returned from ForeignKey function call and stored in model_fields.
<a name="fields.foreign_key.ForeignKeyField.get_source_related_name"></a>
#### get\_source\_related\_name
```python
| @classmethod
| get_source_related_name(cls) -> str
```
Returns name to use for source relation name.
For FK it's the same, differs for m2m fields.
It's either set as `related_name` or by default it's owner model. get_name + 's'
**Returns**:
`(str)`: name of the related_name or default related name.
<a name="fields.foreign_key.ForeignKeyField.get_related_name"></a>
#### get\_related\_name
```python
| @classmethod
| get_related_name(cls) -> str
```
Returns name to use for reverse relation.
It's either set as `related_name` or by default it's owner model. get_name + 's'
**Returns**:
`(str)`: name of the related_name or default related name.
<a name="fields.foreign_key.ForeignKeyField.evaluate_forward_ref"></a>
#### evaluate\_forward\_ref
```python
| @classmethod
| evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
**Arguments**:
- `globalns (Any)`: global namespace
- `localns (Any)`: local namespace
**Returns**:
`(None)`: None
<a name="fields.foreign_key.ForeignKeyField._extract_model_from_sequence"></a>
#### \_extract\_model\_from\_sequence
```python
| @classmethod
| _extract_model_from_sequence(cls, value: List, child: "Model", to_register: bool, relation_name: str) -> List["Model"]
| _extract_model_from_sequence(cls, value: List, child: "Model", to_register: bool) -> List["Model"]
```
Takes a list of Models and registers them on parent.
@ -135,7 +208,7 @@ Used in reverse FK relations.
```python
| @classmethod
| _register_existing_model(cls, value: "Model", child: "Model", to_register: bool, relation_name: str) -> "Model"
| _register_existing_model(cls, value: "Model", child: "Model", to_register: bool) -> "Model"
```
Takes already created instance and registers it for parent.
@ -158,7 +231,7 @@ Used in reverse FK relations and normal FK for single models.
```python
| @classmethod
| _construct_model_from_dict(cls, value: dict, child: "Model", to_register: bool, relation_name: str) -> "Model"
| _construct_model_from_dict(cls, value: dict, child: "Model", to_register: bool) -> "Model"
```
Takes a dictionary, creates a instance and registers it for parent.
@ -182,7 +255,7 @@ Used in normal FK for dictionaries.
```python
| @classmethod
| _construct_model_from_pk(cls, value: Any, child: "Model", to_register: bool, relation_name: str) -> "Model"
| _construct_model_from_pk(cls, value: Any, child: "Model", to_register: bool) -> "Model"
```
Takes a pk value, creates a dummy instance and registers it for parent.
@ -205,7 +278,7 @@ Used in normal FK for dictionaries.
```python
| @classmethod
| register_relation(cls, model: "Model", child: "Model", relation_name: str) -> None
| register_relation(cls, model: "Model", child: "Model") -> None
```
Registers relation between parent and child in relation manager.
@ -219,12 +292,27 @@ Used in Metaclass and sometimes some relations are missing
- `model (Model class)`: parent model (with relation definition)
- `child (Model class)`: child model
<a name="fields.foreign_key.ForeignKeyField.has_unresolved_forward_refs"></a>
#### has\_unresolved\_forward\_refs
```python
| @classmethod
| has_unresolved_forward_refs(cls) -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
model can be used.
**Returns**:
`(bool)`: result of the check
<a name="fields.foreign_key.ForeignKeyField.expand_relationship"></a>
#### expand\_relationship
```python
| @classmethod
| expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True, relation_name: str = None) -> Optional[Union["Model", List["Model"]]]
| expand_relationship(cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True) -> Optional[Union["Model", List["Model"]]]
```
For relations the child model is first constructed (if needed),

View File

@ -1,11 +1,30 @@
<a name="fields.many_to_many"></a>
# fields.many\_to\_many
<a name="fields.many_to_many.populate_m2m_params_based_on_to_model"></a>
#### populate\_m2m\_params\_based\_on\_to\_model
```python
populate_m2m_params_based_on_to_model(to: Type["Model"], nullable: bool) -> Tuple[Any, Any]
```
Based on target to model to which relation leads to populates the type of the
pydantic field to use and type of the target column field.
**Arguments**:
- `to (Model class)`: target related ormar Model
- `nullable (bool)`: marks field as optional/ required
**Returns**:
`(tuple with target pydantic type and target col type)`: Tuple[List, Any]
<a name="fields.many_to_many.ManyToMany"></a>
#### ManyToMany
```python
ManyToMany(to: Type["Model"], through: Type["Model"], *, name: str = None, unique: bool = False, virtual: bool = False, **kwargs: Any) -> Any
ManyToMany(to: Union[Type["Model"], ForwardRef], through: Union[Type["Model"], ForwardRef], *, name: str = None, unique: bool = False, virtual: bool = False, **kwargs: Any, ,) -> Any
```
Despite a name it's a function that returns constructed ManyToManyField.
@ -37,6 +56,22 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationP
Actual class returned from ManyToMany function call and stored in model_fields.
<a name="fields.many_to_many.ManyToManyField.get_source_related_name"></a>
#### get\_source\_related\_name
```python
| @classmethod
| get_source_related_name(cls) -> str
```
Returns name to use for source relation name.
For FK it's the same, differs for m2m fields.
It's either set as `related_name` or by default it's field name.
**Returns**:
`(str)`: name of the related_name or default related name.
<a name="fields.many_to_many.ManyToManyField.default_target_field_name"></a>
#### default\_target\_field\_name
@ -51,3 +86,51 @@ Returns default target model name on through model.
`(str)`: name of the field
<a name="fields.many_to_many.ManyToManyField.default_source_field_name"></a>
#### default\_source\_field\_name
```python
| @classmethod
| default_source_field_name(cls) -> str
```
Returns default target model name on through model.
**Returns**:
`(str)`: name of the field
<a name="fields.many_to_many.ManyToManyField.has_unresolved_forward_refs"></a>
#### has\_unresolved\_forward\_refs
```python
| @classmethod
| has_unresolved_forward_refs(cls) -> bool
```
Verifies if the filed has any ForwardRefs that require updating before the
model can be used.
**Returns**:
`(bool)`: result of the check
<a name="fields.many_to_many.ManyToManyField.evaluate_forward_ref"></a>
#### evaluate\_forward\_ref
```python
| @classmethod
| evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None
```
Evaluates the ForwardRef to actual Field based on global and local namespaces
**Arguments**:
- `globalns (Any)`: global namespace
- `localns (Any)`: local namespace
**Returns**:
`(None)`: None

View File

@ -1,6 +1,24 @@
<a name="models.helpers.models"></a>
# models.helpers.models
<a name="models.helpers.models.is_field_an_forward_ref"></a>
#### is\_field\_an\_forward\_ref
```python
is_field_an_forward_ref(field: Type["BaseField"]) -> bool
```
Checks if field is a relation field and whether any of the referenced models
are ForwardRefs that needs to be updated before proceeding.
**Arguments**:
- `field (Type[BaseField])`: model field to verify
**Returns**:
`(bool)`: result of the check
<a name="models.helpers.models.populate_default_options_values"></a>
#### populate\_default\_options\_values
@ -62,3 +80,28 @@ Also related_names have to be unique for given related model.
- `model_fields (Dict[str, ormar.Field])`: dictionary of declared ormar model fields
- `new_model (Model class)`:
<a name="models.helpers.models.group_related_list"></a>
#### group\_related\_list
```python
group_related_list(list_: List) -> Dict
```
Translates the list of related strings into a dictionary.
That way nested models are grouped to traverse them in a right order
and to avoid repetition.
Sample: ["people__houses", "people__cars__models", "people__cars__colors"]
will become:
{'people': {'houses': [], 'cars': ['models', 'colors']}}
Result dictionary is sorted by length of the values and by key
**Arguments**:
- `list_ (List[str])`: list of related models used in select related
**Returns**:
`(Dict[str, List])`: list converted to dictionary to avoid repetition and group nested models

View File

@ -5,7 +5,7 @@
#### register\_relation\_on\_build
```python
register_relation_on_build(new_model: Type["Model"], field_name: str) -> None
register_relation_on_build(field: Type["ForeignKeyField"]) -> None
```
Registers ForeignKey relation in alias_manager to set a table_prefix.
@ -17,14 +17,13 @@ aliases for proper sql joins.
**Arguments**:
- `new_model (Model class)`: constructed model
- `field_name (str)`: name of the related field
- `field (ForeignKey class)`: relation field
<a name="models.helpers.relations.register_many_to_many_relation_on_build"></a>
#### register\_many\_to\_many\_relation\_on\_build
```python
register_many_to_many_relation_on_build(new_model: Type["Model"], field: Type[ManyToManyField], field_name: str) -> None
register_many_to_many_relation_on_build(field: Type[ManyToManyField]) -> None
```
Registers connection between through model and both sides of the m2m relation.
@ -38,10 +37,25 @@ By default relation name is a model.name.lower().
**Arguments**:
- `field_name (str)`: name of the relation key
- `new_model (Model class)`: model on which m2m field is declared
- `field (ManyToManyField class)`: relation field
<a name="models.helpers.relations.expand_reverse_relationship"></a>
#### expand\_reverse\_relationship
```python
expand_reverse_relationship(model_field: Type["ForeignKeyField"]) -> None
```
If the reverse relation has not been set before it's set here.
**Arguments**:
- `model_field ()`:
**Returns**:
`(None)`: None
<a name="models.helpers.relations.expand_reverse_relationships"></a>
#### expand\_reverse\_relationships
@ -62,7 +76,7 @@ If the reverse relation has not been set before it's set here.
#### register\_reverse\_model\_fields
```python
register_reverse_model_fields(model: Type["Model"], child: Type["Model"], related_name: str, model_field: Type["ForeignKeyField"]) -> None
register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None
```
Registers reverse ForeignKey field on related model.
@ -73,16 +87,13 @@ Autogenerated reverse fields also set related_name to the original field name.
**Arguments**:
- `model (Model class)`: related model on which reverse field should be defined
- `child (Model class)`: parent model with relation definition
- `related_name (str)`: name by which reverse key should be registered
- `model_field (relation Field)`: original relation ForeignKey field
<a name="models.helpers.relations.register_relation_in_alias_manager"></a>
#### register\_relation\_in\_alias\_manager
```python
register_relation_in_alias_manager(new_model: Type["Model"], field: Type[ForeignKeyField], field_name: str) -> None
register_relation_in_alias_manager(field: Type[ForeignKeyField]) -> None
```
Registers the relation (and reverse relation) in alias manager.
@ -95,15 +106,13 @@ fk - register_relation_on_build
**Arguments**:
- `new_model (Model class)`: model on which relation field is declared
- `field (ForeignKey or ManyToManyField class)`: relation field
- `field_name (str)`: name of the relation key
<a name="models.helpers.relations.verify_related_name_dont_duplicate"></a>
#### verify\_related\_name\_dont\_duplicate
```python
verify_related_name_dont_duplicate(child: Type["Model"], parent_model: Type["Model"], related_name: str) -> None
verify_related_name_dont_duplicate(related_name: str, model_field: Type["ForeignKeyField"]) -> None
```
Verifies whether the used related_name (regardless of the fact if user defined or
@ -117,9 +126,8 @@ model
**Arguments**:
- `child (ormar.models.metaclass.ModelMetaclass)`: related Model class
- `parent_model (ormar.models.metaclass.ModelMetaclass)`: parent Model class
- `related_name ()`:
- `model_field (relation Field)`: original relation ForeignKey field
**Returns**:
@ -129,7 +137,7 @@ model
#### reverse\_field\_not\_already\_registered
```python
reverse_field_not_already_registered(child: Type["Model"], child_model_name: str, parent_model: Type["Model"]) -> bool
reverse_field_not_already_registered(model_field: Type["ForeignKeyField"]) -> bool
```
Checks if child is already registered in parents pydantic fields.
@ -141,9 +149,7 @@ related model
**Arguments**:
- `child (ormar.models.metaclass.ModelMetaclass)`: related Model class
- `child_model_name (str)`: related_name of the child if provided
- `parent_model (ormar.models.metaclass.ModelMetaclass)`: parent Model class
- `model_field (relation Field)`: original relation ForeignKey field
**Returns**:

View File

@ -5,7 +5,7 @@
#### adjust\_through\_many\_to\_many\_model
```python
adjust_through_many_to_many_model(model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField]) -> None
adjust_through_many_to_many_model(model_field: Type[ManyToManyField]) -> None
```
Registers m2m relation on through model.
@ -15,23 +15,22 @@ Sets pydantic fields with child and parent model types.
**Arguments**:
- `model (Model class)`: model on which relation is declared
- `child (Model class)`: model to which m2m relation leads
- `model_field (ManyToManyField)`: relation field defined in parent model
<a name="models.helpers.sqlalchemy.create_and_append_m2m_fk"></a>
#### create\_and\_append\_m2m\_fk
```python
create_and_append_m2m_fk(model: Type["Model"], model_field: Type[ManyToManyField]) -> None
create_and_append_m2m_fk(model: Type["Model"], model_field: Type[ManyToManyField], field_name: str) -> None
```
Registers sqlalchemy Column with sqlalchemy.ForeignKey leadning to the model.
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
Newly created field is added to m2m relation through model Meta columns and table.
**Arguments**:
- `field_name (str)`: name of the column to create
- `model (Model class)`: Model class to which FK should be created
- `model_field (ManyToManyField field)`: field with ManyToMany relation
@ -83,6 +82,8 @@ cannot be pydantic_only.
Append fields to columns if it's not pydantic_only,
virtual ForeignKey or ManyToMany field.
Sets `owner` on each model_field as reference to newly created Model.
**Raises**:
- `ModelDefinitionError`: if validation of related_names fail,
@ -125,6 +126,23 @@ Each model has to have pk.
`(ormar.models.metaclass.ModelMetaclass)`: Model with populated pkname and columns in Meta
<a name="models.helpers.sqlalchemy.check_for_null_type_columns_from_forward_refs"></a>
#### check\_for\_null\_type\_columns\_from\_forward\_refs
```python
check_for_null_type_columns_from_forward_refs(meta: "ModelMeta") -> bool
```
Check is any column is of NUllType() meaning it's empty column from ForwardRef
**Arguments**:
- `meta (Model class Meta)`: Meta class of the Model without sqlalchemy table constructed
**Returns**:
`(bool)`: result of the check
<a name="models.helpers.sqlalchemy.populate_meta_sqlalchemy_table_if_required"></a>
#### populate\_meta\_sqlalchemy\_table\_if\_required
@ -143,3 +161,21 @@ It populates name, metadata, columns and constraints.
`(Model class)`: class with populated Meta.table
<a name="models.helpers.sqlalchemy.update_column_definition"></a>
#### update\_column\_definition
```python
update_column_definition(model: Union[Type["Model"], Type["NewBaseModel"]], field: Type[ForeignKeyField]) -> None
```
Updates a column with a new type column based on updated parameters in FK fields.
**Arguments**:
- `model (Type["Model"])`: model on which columns needs to be updated
- `field (Type[ForeignKeyField])`: field with column definition that requires update
**Returns**:
`(None)`: None

View File

@ -59,7 +59,7 @@ or field name specified by related parameter.
```python
| @classmethod
| get_related_field_name(cls, target_field: Type["BaseField"]) -> str
| get_related_field_name(cls, target_field: Type["ForeignKeyField"]) -> str
```
Returns name of the relation field that should be used in prefetch query.

View File

@ -1,6 +1,17 @@
<a name="models.metaclass"></a>
# models.metaclass
<a name="models.metaclass.ModelMeta"></a>
## ModelMeta Objects
```python
class ModelMeta()
```
Class used for type hinting.
Users can subclass this one for convenience but it's not required.
The only requirement is that ormar.Model has to have inner class with name Meta.
<a name="models.metaclass.check_if_field_has_choices"></a>
#### check\_if\_field\_has\_choices
@ -143,7 +154,7 @@ as well as model.Meta.model_fields definitions from parents.
**Arguments**:
- `attrs (Dict)`: new namespace for class being constructed
- `new_attrs (Dict)`: part of the namespace extracted from parent class
- `new_attrs (Dict)`: related of the namespace extracted from parent class
- `model_fields (Dict[str, BaseField])`: ormar fields in defined in current class
- `new_model_fields (Dict[str, BaseField])`: ormar fields defined in parent classes
- `new_fields (Set[str])`: set of new fields names
@ -270,18 +281,6 @@ If the class is a ormar.Model it is skipped.
`(Tuple[Dict, Dict])`: updated attrs and model_fields
<a name="models.metaclass.ModelMeta"></a>
## ModelMeta Objects
```python
class ModelMeta()
```
Class used for type hinting.
Users can subclass this one for convenience but it's not required.
The only requirement is that ormar.Model has to have inner class with name Meta.
<a name="models.metaclass.ModelMetaclass"></a>
## ModelMetaclass Objects

View File

@ -1,29 +1,6 @@
<a name="models.model"></a>
# models.model
<a name="models.model.group_related_list"></a>
#### group\_related\_list
```python
group_related_list(list_: List) -> Dict
```
Translates the list of related strings into a dictionary.
That way nested models are grouped to traverse them in a right order
and to avoid repetition.
Sample: ["people__houses", "people__cars__models", "people__cars__colors"]
will become:
{'people': {'houses': [], 'cars': ['models', 'colors']}}
**Arguments**:
- `list_ (List[str])`: list of related models used in select related
**Returns**:
`(Dict[str, List])`: list converted to dictionary to avoid repetition and group nested models
<a name="models.model.Model"></a>
## Model Objects
@ -36,7 +13,7 @@ class Model(NewBaseModel)
```python
| @classmethod
| from_row(cls: Type[T], row: sqlalchemy.engine.ResultProxy, select_related: List = None, related_models: Any = None, previous_model: Type[T] = None, related_name: str = None, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None) -> Optional[T]
| from_row(cls: Type[T], row: sqlalchemy.engine.ResultProxy, select_related: List = None, related_models: Any = None, previous_model: Type[T] = None, source_model: Type[T] = None, related_name: str = None, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, current_relation_str: str = None) -> Optional[T]
```
Model method to convert raw sql row from database into ormar.Model instance.
@ -72,7 +49,7 @@ excludes the fields even if they are provided in fields
```python
| @classmethod
| populate_nested_models_from_row(cls, item: dict, row: sqlalchemy.engine.ResultProxy, related_models: Any, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None) -> dict
| populate_nested_models_from_row(cls, item: dict, row: sqlalchemy.engine.ResultProxy, related_models: Any, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, current_relation_str: str = None, source_model: Type[T] = None) -> dict
```
Traverses structure of related models and populates the nested models
@ -86,6 +63,8 @@ instances. In the end those instances are added to the final model dictionary.
**Arguments**:
- `source_model (Type[Model])`: source model from which relation started
- `current_relation_str (str)`: joined related parts into one string
- `item (Dict)`: dictionary of already populated nested models, otherwise empty dict
- `row (sqlalchemy.engine.result.ResultProxy)`: raw result row from the database
- `related_models (Union[Dict, List])`: list or dict of related models
@ -114,7 +93,7 @@ If the table is a main table, there is no prefix.
All joined tables have prefixes to allow duplicate column names,
as well as duplicated joins to the same table from multiple different tables.
Extracted fields populates the item dict later used to construct a Model.
Extracted fields populates the related dict later used to construct a Model.
Used in Model.from_row and PrefetchQuery._populate_rows methods.

View File

@ -48,7 +48,8 @@ them with their default values if default is set.
**Raises**:
- `ModelError`: if abstract model is initialized or unknown field is passed
- `ModelError`: if abstract model is initialized, model has ForwardRefs
that has not been updated or unknown field is passed
**Arguments**:
@ -128,6 +129,19 @@ Json fields are converted if needed.
`(Any)`: value of the attribute
<a name="models.newbasemodel.NewBaseModel._verify_model_can_be_initialized"></a>
#### \_verify\_model\_can\_be\_initialized
```python
| _verify_model_can_be_initialized() -> None
```
Raises exception if model is abstract or has ForwardRefs in relation fields.
**Returns**:
`(None)`: None
<a name="models.newbasemodel.NewBaseModel._extract_related_model_instead_of_field"></a>
#### \_extract\_related\_model\_instead\_of\_field
@ -299,6 +313,34 @@ present in fastapi responses.
`(Set[str])`: set of property fields names
<a name="models.newbasemodel.NewBaseModel.update_forward_refs"></a>
#### update\_forward\_refs
```python
| @classmethod
| update_forward_refs(cls, **localns: Any) -> None
```
Processes fields that are ForwardRef and need to be evaluated into actual
models.
Expands relationships, register relation in alias manager and substitutes
sqlalchemy columns with new ones with proper column type (null before).
Populates Meta table of the Model which is left empty before.
Sets self_reference flag on models that links to themselves.
Calls the pydantic method to evaluate pydantic fields.
**Arguments**:
- `localns (Any)`: local namespace
**Returns**:
`(None)`: None
<a name="models.newbasemodel.NewBaseModel._get_related_not_excluded_fields"></a>
#### \_get\_related\_not\_excluded\_fields

View File

@ -8,13 +8,13 @@
class QueryClause()
```
Constructs where clauses from strings passed as arguments
Constructs FilterActions from strings passed as arguments
<a name="queryset.clause.QueryClause.filter"></a>
#### filter
<a name="queryset.clause.QueryClause.prepare_filter"></a>
#### prepare\_filter
```python
| filter(**kwargs: Any) -> Tuple[List[sqlalchemy.sql.expression.TextClause], List[str]]
| prepare_filter(**kwargs: Any) -> Tuple[List[FilterAction], List[str]]
```
Main external access point that processes the clauses into sqlalchemy text
@ -33,7 +33,7 @@ mentioned in select_related strings but not included in select_related.
#### \_populate\_filter\_clauses
```python
| _populate_filter_clauses(**kwargs: Any) -> Tuple[List[sqlalchemy.sql.expression.TextClause], List[str]]
| _populate_filter_clauses(**kwargs: Any) -> Tuple[List[FilterAction], List[str]]
```
Iterates all clauses and extracts used operator and field from related
@ -48,114 +48,59 @@ is determined and the final clause is escaped if needed and compiled.
`(Tuple[List[sqlalchemy.sql.elements.TextClause], List[str]])`: Tuple with list of where clauses and updated select_related list
<a name="queryset.clause.QueryClause._process_column_clause_for_operator_and_value"></a>
#### \_process\_column\_clause\_for\_operator\_and\_value
<a name="queryset.clause.QueryClause._register_complex_duplicates"></a>
#### \_register\_complex\_duplicates
```python
| _process_column_clause_for_operator_and_value(value: Any, op: str, column: sqlalchemy.Column, table: sqlalchemy.Table, table_prefix: str) -> sqlalchemy.sql.expression.TextClause
| _register_complex_duplicates(select_related: List[str]) -> None
```
Escapes characters if it's required.
Substitutes values of the models if value is a ormar Model with its pk value.
Compiles the clause.
Checks if duplicate aliases are presented which can happen in self relation
or when two joins end with the same pair of models.
If there are duplicates, the all duplicated joins are registered as source
model and whole relation key (not just last relation name).
**Arguments**:
- `value (Any)`: value of the filter
- `op (str)`: filter operator
- `column (sqlalchemy.sql.schema.Column)`: column on which filter should be applied
- `table (sqlalchemy.sql.schema.Table)`: table on which filter should be applied
- `table_prefix (str)`: prefix from AliasManager
- `select_related (List[str])`: list of relation strings
**Returns**:
`(sqlalchemy.sql.elements.TextClause)`: complied and escaped clause
`(None)`: None
<a name="queryset.clause.QueryClause._determine_filter_target_table"></a>
#### \_determine\_filter\_target\_table
<a name="queryset.clause.QueryClause._parse_related_prefixes"></a>
#### \_parse\_related\_prefixes
```python
| _determine_filter_target_table(related_parts: List[str], select_related: List[str]) -> Tuple[List[str], str, Type["Model"]]
| _parse_related_prefixes(select_related: List[str]) -> List[Prefix]
```
Adds related strings to select_related list otherwise the clause would fail as
the required columns would not be present. That means that select_related
list is filled with missing values present in filters.
Walks the relation to retrieve the actual model on which the clause should be
constructed, extracts alias based on last relation leading to target model.
Walks all relation strings and parses the target models and prefixes.
**Arguments**:
- `related_parts (List[str])`: list of split parts of related string
- `select_related (List[str])`: list of related models
- `select_related (List[str])`: list of relation strings
**Returns**:
`(Tuple[List[str], str, Type[Model]])`: list of related models, table_prefix, final model class
`(List[Prefix])`: list of parsed prefixes
<a name="queryset.clause.QueryClause._compile_clause"></a>
#### \_compile\_clause
<a name="queryset.clause.QueryClause._switch_filter_action_prefixes"></a>
#### \_switch\_filter\_action\_prefixes
```python
| _compile_clause(clause: sqlalchemy.sql.expression.BinaryExpression, column: sqlalchemy.Column, table: sqlalchemy.Table, table_prefix: str, modifiers: Dict) -> sqlalchemy.sql.expression.TextClause
| _switch_filter_action_prefixes(filter_clauses: List[FilterAction]) -> List[FilterAction]
```
Compiles the clause to str using appropriate database dialect, replace columns
names with aliased names and converts it back to TextClause.
Substitutes aliases for filter action if the complex key (whole relation str) is
present in alias_manager.
**Arguments**:
- `clause (sqlalchemy.sql.elements.BinaryExpression)`: original not compiled clause
- `column (sqlalchemy.sql.schema.Column)`: column on which filter should be applied
- `table (sqlalchemy.sql.schema.Table)`: table on which filter should be applied
- `table_prefix (str)`: prefix from AliasManager
- `modifiers (Dict[str, NoneType])`: sqlalchemy modifiers - used only to escape chars here
- `filter_clauses (List[FilterAction])`: raw list of actions
**Returns**:
`(sqlalchemy.sql.elements.TextClause)`: compiled and escaped clause
<a name="queryset.clause.QueryClause._escape_characters_in_clause"></a>
#### \_escape\_characters\_in\_clause
```python
| @staticmethod
| _escape_characters_in_clause(op: str, value: Any) -> Tuple[Any, bool]
```
Escapes the special characters ["%", "_"] if needed.
Adds `%` for `like` queries.
**Raises**:
- `QueryDefinitionError`: if contains or icontains is used with
ormar model instance
**Arguments**:
- `op (str)`: operator used in query
- `value (Any)`: value of the filter
**Returns**:
`(Tuple[Any, bool])`: escaped value and flag if escaping is needed
<a name="queryset.clause.QueryClause._extract_operator_field_and_related"></a>
#### \_extract\_operator\_field\_and\_related
```python
| @staticmethod
| _extract_operator_field_and_related(parts: List[str]) -> Tuple[str, str, Optional[List]]
```
Splits filter query key and extracts required parts.
**Arguments**:
- `parts (List[str])`: split filter query key
**Returns**:
`(Tuple[str, str, Optional[List]])`: operator, field_name, list of related parts
`(List[FilterAction])`: list of actions with aliases changed if needed

View File

@ -1,15 +1,6 @@
<a name="queryset.join"></a>
# queryset.join
<a name="queryset.join.JoinParameters"></a>
## JoinParameters Objects
```python
class JoinParameters(NamedTuple)
```
Named tuple that holds set of parameters passed during join construction.
<a name="queryset.join.SqlJoin"></a>
## SqlJoin Objects
@ -21,15 +12,11 @@ class SqlJoin()
#### alias\_manager
```python
| @staticmethod
| alias_manager(model_cls: Type["Model"]) -> AliasManager
| @property
| alias_manager() -> AliasManager
```
Shortcut for ormars model AliasManager stored on Meta.
**Arguments**:
- `model_cls (Type[Model])`: ormar Model class
Shortcut for ormar's model AliasManager stored on Meta.
**Returns**:
@ -39,8 +26,7 @@ Shortcut for ormars model AliasManager stored on Meta.
#### on\_clause
```python
| @staticmethod
| on_clause(previous_alias: str, alias: str, from_clause: str, to_clause: str) -> text
| on_clause(previous_alias: str, from_clause: str, to_clause: str) -> text
```
Receives aliases and names of both ends of the join and combines them
@ -49,7 +35,6 @@ into one text clause used in joins.
**Arguments**:
- `previous_alias (str)`: alias of previous table
- `alias (str)`: alias of current table
- `from_clause (str)`: from table name
- `to_clause (str)`: to table name
@ -57,32 +42,11 @@ into one text clause used in joins.
`(sqlalchemy.text)`: clause combining all strings
<a name="queryset.join.SqlJoin.update_inclusions"></a>
#### update\_inclusions
```python
| @staticmethod
| update_inclusions(model_cls: Type["Model"], fields: Optional[Union[Set, Dict]], exclude_fields: Optional[Union[Set, Dict]], nested_name: str) -> Tuple[Optional[Union[Dict, Set]], Optional[Union[Dict, Set]]]
```
Extract nested fields and exclude_fields if applicable.
**Arguments**:
- `model_cls (Type["Model"])`: ormar model class
- `fields (Optional[Union[Set, Dict]])`: fields to include
- `exclude_fields (Optional[Union[Set, Dict]])`: fields to exclude
- `nested_name (str)`: name of the nested field
**Returns**:
`(Tuple[Optional[Union[Dict, Set]], Optional[Union[Dict, Set]]])`: updated exclude and include fields from nested objects
<a name="queryset.join.SqlJoin.build_join"></a>
#### build\_join
```python
| build_join(item: str, join_parameters: JoinParameters) -> Tuple[List, sqlalchemy.sql.select, List, OrderedDict]
| build_join() -> Tuple[List, sqlalchemy.sql.select, List, OrderedDict]
```
Main external access point for building a join.
@ -90,42 +54,96 @@ Splits the join definition, updates fields and exclude_fields if needed,
handles switching to through models for m2m relations, returns updated lists of
used_aliases and sort_orders.
**Arguments**:
- `item (str)`: string with join definition
- `join_parameters (JoinParameters)`: parameters from previous/ current join
**Returns**:
`(Tuple[List[str], Join, List[TextClause], collections.OrderedDict])`: list of used aliases, select from, list of aliased columns, sort orders
<a name="queryset.join.SqlJoin._build_join_parameters"></a>
#### \_build\_join\_parameters
<a name="queryset.join.SqlJoin._forward_join"></a>
#### \_forward\_join
```python
| _build_join_parameters(part: str, join_params: JoinParameters, fields: Optional[Union[Set, Dict]], exclude_fields: Optional[Union[Set, Dict]], is_multi: bool = False) -> JoinParameters
| _forward_join() -> None
```
Updates used_aliases to not join multiple times to the same table.
Updates join parameters with new values.
Process actual join.
Registers complex relation join on encountering of the duplicated alias.
<a name="queryset.join.SqlJoin._process_following_joins"></a>
#### \_process\_following\_joins
```python
| _process_following_joins() -> None
```
Iterates through nested models to create subsequent joins.
<a name="queryset.join.SqlJoin._process_deeper_join"></a>
#### \_process\_deeper\_join
```python
| _process_deeper_join(related_name: str, remainder: Any) -> None
```
Creates nested recurrent instance of SqlJoin for each nested join table,
updating needed return params here as a side effect.
Updated are:
* self.used_aliases,
* self.select_from,
* self.columns,
* self.sorted_orders,
**Arguments**:
- `part (str)`: part of the join str definition
- `join_params (JoinParameters)`: parameters from previous/ current join
- `fields (Optional[Union[Set, Dict]])`: fields to include
- `exclude_fields (Optional[Union[Set, Dict]])`: fields to exclude
- `is_multi (bool)`: flag if the relation is m2m
- `related_name (str)`: name of the relation to follow
- `remainder (Any)`: deeper tables if there are more nested joins
<a name="queryset.join.SqlJoin.process_m2m_through_table"></a>
#### process\_m2m\_through\_table
```python
| process_m2m_through_table() -> None
```
Process Through table of the ManyToMany relation so that source table is
linked to the through table (one additional join)
Replaces needed parameters like:
* self.next_model,
* self.next_alias,
* self.relation_name,
* self.own_alias,
* self.target_field
To point to through model
<a name="queryset.join.SqlJoin.process_m2m_related_name_change"></a>
#### process\_m2m\_related\_name\_change
```python
| process_m2m_related_name_change(reverse: bool = False) -> str
```
Extracts relation name to link join through the Through model declared on
relation field.
Changes the same names in order_by queries if they are present.
**Arguments**:
- `reverse (bool)`: flag if it's on_clause lookup - use reverse fields
**Returns**:
`(ormar.queryset.join.JoinParameters)`: updated join parameters
`(str)`: new relation name switched to through model field
<a name="queryset.join.SqlJoin._process_join"></a>
#### \_process\_join
```python
| _process_join(join_params: JoinParameters, is_multi: bool, model_cls: Type["Model"], part: str, alias: str, fields: Optional[Union[Set, Dict]], exclude_fields: Optional[Union[Set, Dict]]) -> None
| _process_join() -> None
```
Resolves to and from column names and table names.
@ -140,18 +158,8 @@ Updates the used aliases list directly.
Process order_by causes for non m2m relations.
**Arguments**:
- `join_params (JoinParameters)`: parameters from previous/ current join
- `is_multi (bool)`: flag if it's m2m relation
- `model_cls (ormar.models.metaclass.ModelMetaclass)`:
- `part (str)`: name of the field used in join
- `alias (str)`: alias of the current join
- `fields (Optional[Union[Set, Dict]])`: fields to include
- `exclude_fields (Optional[Union[Set, Dict]])`: fields to exclude
<a name="queryset.join.SqlJoin._replace_many_to_many_order_by_columns"></a>
#### \_switch\_many\_to\_many\_order\_columns
#### \_replace\_many\_to\_many\_order\_by\_columns
```python
| _replace_many_to_many_order_by_columns(part: str, new_part: str) -> None
@ -187,7 +195,7 @@ Checks filter conditions to find if they apply to current join.
#### set\_aliased\_order\_by
```python
| set_aliased_order_by(condition: List[str], alias: str, to_table: str, model_cls: Type["Model"]) -> None
| set_aliased_order_by(condition: List[str], to_table: str) -> None
```
Substitute hyphens ('-') with descending order.
@ -196,15 +204,13 @@ Construct actual sqlalchemy text clause using aliased table and column name.
**Arguments**:
- `condition (List[str])`: list of parts of a current condition split by '__'
- `alias (str)`: alias of the table in current join
- `to_table (sqlalchemy.sql.elements.quoted_name)`: target table
- `model_cls (ormar.models.metaclass.ModelMetaclass)`: ormar model class
<a name="queryset.join.SqlJoin.get_order_bys"></a>
#### get\_order\_bys
```python
| get_order_bys(alias: str, to_table: str, pkname_alias: str, part: str, model_cls: Type["Model"]) -> None
| get_order_bys(to_table: str, pkname_alias: str) -> None
```
Triggers construction of order bys if they are given.
@ -212,30 +218,19 @@ Otherwise by default each table is sorted by a primary key column asc.
**Arguments**:
- `alias (str)`: alias of current table in join
- `to_table (sqlalchemy.sql.elements.quoted_name)`: target table
- `pkname_alias (str)`: alias of the primary key column
- `part (str)`: name of the current relation join
- `model_cls (Type[Model])`: ormar model class
<a name="queryset.join.SqlJoin.get_to_and_from_keys"></a>
#### get\_to\_and\_from\_keys
```python
| @staticmethod
| get_to_and_from_keys(join_params: JoinParameters, is_multi: bool, model_cls: Type["Model"], part: str) -> Tuple[str, str]
| get_to_and_from_keys() -> Tuple[str, str]
```
Based on the relation type, name of the relation and previous models and parts
stored in JoinParameters it resolves the current to and from keys, which are
different for ManyToMany relation, ForeignKey and reverse part of relations.
**Arguments**:
- `join_params (JoinParameters)`: parameters from previous/ current join
- `is_multi (bool)`: flag if the relation is of m2m type
- `model_cls (Type[Model])`: ormar model class
- `part (str)`: name of the current relation join
different for ManyToMany relation, ForeignKey and reverse related of relations.
**Returns**:

View File

@ -289,7 +289,7 @@ models.
| _get_select_related_if_apply(related: str, select_dict: Dict) -> Dict
```
Extract nested part of select_related dictionary to extract models nested
Extract nested related of select_related dictionary to extract models nested
deeper on related model and already loaded in select related query.
**Arguments**:
@ -299,7 +299,7 @@ deeper on related model and already loaded in select related query.
**Returns**:
`(Dict)`: dictionary with nested part of select related
`(Dict)`: dictionary with nested related of select related
<a name="queryset.prefetch_query.PrefetchQuery._update_already_loaded_rows"></a>
#### \_update\_already\_loaded\_rows
@ -320,7 +320,7 @@ Updates models that are already loaded, usually children of children.
#### \_populate\_rows
```python
| _populate_rows(rows: List, target_field: Type["BaseField"], parent_model: Type["Model"], table_prefix: str, fields: Union[Set[Any], Dict[Any, Any], None], exclude_fields: Union[Set[Any], Dict[Any, Any], None], prefetch_dict: Dict, orders_by: Dict) -> None
| _populate_rows(rows: List, target_field: Type["ForeignKeyField"], parent_model: Type["Model"], table_prefix: str, fields: Union[Set[Any], Dict[Any, Any], None], exclude_fields: Union[Set[Any], Dict[Any, Any], None], prefetch_dict: Dict, orders_by: Dict) -> None
```
Instantiates children models extracted from given relation.

View File

@ -150,3 +150,22 @@ with all children models under their relation keys.
`(Dict)`: dictionary of lists f related models
<a name="queryset.utils.get_relationship_alias_model_and_str"></a>
#### get\_relationship\_alias\_model\_and\_str
```python
get_relationship_alias_model_and_str(source_model: Type["Model"], related_parts: List) -> Tuple[str, Type["Model"], str]
```
Walks the relation to retrieve the actual model on which the clause should be
constructed, extracts alias based on last relation leading to target model.
**Arguments**:
- `related_parts (Union[List, List[str]])`: list of related names extracted from string
- `source_model (Type[Model])`: model from which relation starts
**Returns**:
`(Tuple[str, Type["Model"], str])`: table prefix, target model and relation string

View File

@ -74,7 +74,7 @@ Creates text clause with table name with aliased name.
#### add\_relation\_type
```python
| add_relation_type(source_model: Type["Model"], relation_name: str, reverse_name: str = None, is_multi: bool = False) -> None
| add_relation_type(source_model: Type["Model"], relation_name: str, reverse_name: str = None) -> None
```
Registers the relations defined in ormar models.
@ -94,12 +94,28 @@ on one model as well as from multiple different models in one join.
- `source_model (source Model)`: model with relation defined
- `relation_name (str)`: name of the relation to define
- `reverse_name (Optional[str])`: name of related_name fo given relation for m2m relations
- `is_multi (bool)`: flag if relation being registered is a through m2m model
**Returns**:
`(None)`: none
<a name="relations.alias_manager.AliasManager.add_alias"></a>
#### add\_alias
```python
| add_alias(alias_key: str) -> str
```
Adds alias to the dictionary of aliases under given key.
**Arguments**:
- `alias_key (str)`: key of relation to generate alias for
**Returns**:
`(str)`: generated alias
<a name="relations.alias_manager.AliasManager.resolve_relation_alias"></a>
#### resolve\_relation\_alias

View File

@ -98,7 +98,7 @@ Returns the actual relation and not the related model(s).
```python
| @staticmethod
| add(parent: "Model", child: "Model", child_name: str, virtual: bool, relation_name: str) -> None
| add(parent: "Model", child: "Model", field: Type["ForeignKeyField"]) -> None
```
Adds relation on both sides -> meaning on both child and parent models.
@ -112,9 +112,7 @@ on both ends.
- `parent (Model)`: parent model on which relation should be registered
- `child (Model)`: child model to register
- `child_name (str)`: potential child name used if related name is not set
- `virtual (bool)`:
- `relation_name (str)`: name of the relation
- `field (ForeignKeyField)`: field with relation definition
<a name="relations.relation_manager.RelationsManager.remove"></a>
#### remove

View File

@ -114,7 +114,7 @@ to the parent model only, without need for user to filter them.
| async remove(item: "Model", keep_reversed: bool = True) -> None
```
Removes the item from relation with parent.
Removes the related from relation with parent.
Through models are automatically deleted for m2m relations.

View File

@ -5,7 +5,7 @@
#### get\_relations\_sides\_and\_names
```python
get_relations_sides_and_names(to_field: Type[BaseField], parent: "Model", child: "Model", child_name: str, virtual: bool, relation_name: str) -> Tuple["Model", "Model", str, str]
get_relations_sides_and_names(to_field: Type[ForeignKeyField], parent: "Model", child: "Model") -> Tuple["Model", "Model", str, str]
```
Determines the names of child and parent relations names, as well as
@ -13,12 +13,9 @@ changes one of the sides of the relation into weakref.proxy to model.
**Arguments**:
- `to_field (BaseField)`: field with relation definition
- `to_field (ForeignKeyField)`: field with relation definition
- `parent (Model)`: parent model
- `child (Model)`: child model
- `child_name (str)`: name of the child
- `virtual (bool)`: flag if relation is virtual
- `relation_name ()`:
**Returns**: