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:
@ -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
|
||||
|
||||
|
||||
@ -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**:
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user