mostly working

This commit is contained in:
collerek
2020-08-16 22:27:39 +02:00
parent b69ad226e6
commit a39179bc64
18 changed files with 988 additions and 536 deletions

View File

@ -32,7 +32,7 @@ class QueryClause:
self.filter_clauses = filter_clauses
self.model_cls = model_cls
self.table = self.model_cls.__table__
self.table = self.model_cls.Meta.table
def filter( # noqa: A003
self, **kwargs: Any
@ -41,7 +41,7 @@ class QueryClause:
select_related = list(self._select_related)
if kwargs.get("pk"):
pk_name = self.model_cls.__pkname__
pk_name = self.model_cls.Meta.pkname
kwargs[pk_name] = kwargs.pop("pk")
for key, value in kwargs.items():
@ -65,8 +65,8 @@ class QueryClause:
related_parts, select_related
)
table = model_cls.__table__
column = model_cls.__table__.columns[field_name]
table = model_cls.Meta.table
column = model_cls.Meta.table.columns[field_name]
else:
op = "exact"
@ -106,12 +106,12 @@ class QueryClause:
# Walk the relationships to the actual model class
# against which the comparison is being made.
previous_table = model_cls.__tablename__
previous_table = model_cls.Meta.tablename
for part in related_parts:
current_table = model_cls.__model_fields__[part].to.__tablename__
manager = model_cls._orm_relationship_manager
current_table = model_cls.Meta.model_fields[part].to.Meta.tablename
manager = model_cls.Meta._orm_relationship_manager
table_prefix = manager.resolve_relation_join(previous_table, current_table)
model_cls = model_cls.__model_fields__[part].to
model_cls = model_cls.Meta.model_fields[part].to
previous_table = current_table
return select_related, table_prefix, model_cls
@ -128,7 +128,7 @@ class QueryClause:
clause_text = str(
clause.compile(
dialect=self.model_cls.__database__._backend._dialect,
dialect=self.model_cls.Meta.database._backend._dialect,
compile_kwargs={"literal_binds": True},
)
)

View File

@ -4,8 +4,8 @@ import sqlalchemy
from sqlalchemy import text
import ormar # noqa I100
from ormar import ForeignKey
from ormar.fields import BaseField
from ormar.fields.foreign_key import ForeignKeyField
if TYPE_CHECKING: # pragma no cover
from ormar import Model
@ -20,12 +20,12 @@ class JoinParameters(NamedTuple):
class Query:
def __init__(
self,
model_cls: Type["Model"],
filter_clauses: List,
select_related: List,
limit_count: int,
offset: int,
self,
model_cls: Type["Model"],
filter_clauses: List,
select_related: List,
limit_count: int,
offset: int,
) -> None:
self.query_offset = offset
@ -34,7 +34,7 @@ class Query:
self.filter_clauses = filter_clauses
self.model_cls = model_cls
self.table = self.model_cls.__table__
self.table = self.model_cls.Meta.table
self.auto_related = []
self.used_aliases = []
@ -46,16 +46,16 @@ class Query:
def build_select_expression(self) -> Tuple[sqlalchemy.sql.select, List[str]]:
self.columns = list(self.table.columns)
self.order_bys = [text(f"{self.table.name}.{self.model_cls.__pkname__}")]
self.order_bys = [text(f"{self.table.name}.{self.model_cls.Meta.pkname}")]
self.select_from = self.table
for key in self.model_cls.__model_fields__:
for key in self.model_cls.Meta.model_fields:
if (
not self.model_cls.__model_fields__[key].nullable
and isinstance(
self.model_cls.__model_fields__[key], ormar.fields.ForeignKey,
)
and key not in self._select_related
not self.model_cls.Meta.model_fields[key].nullable
and isinstance(
self.model_cls.Meta.model_fields[key], ForeignKeyField,
)
and key not in self._select_related
):
self._select_related = [key] + self._select_related
@ -79,7 +79,7 @@ class Query:
expr = self._apply_expression_modifiers(expr)
# print(expr.compile(compile_kwargs={"literal_binds": True}))
print(expr.compile(compile_kwargs={"literal_binds": True}))
self._reset_query_parameters()
return expr, self._select_related
@ -97,12 +97,12 @@ class Query:
@staticmethod
def _field_is_a_foreign_key_and_no_circular_reference(
field: BaseField, field_name: str, rel_part: str
field: BaseField, field_name: str, rel_part: str
) -> bool:
return isinstance(field, ForeignKey) and field_name not in rel_part
return issubclass(field, ForeignKeyField) and field_name not in rel_part
def _field_qualifies_to_deeper_search(
self, field: ForeignKey, parent_virtual: bool, nested: bool, rel_part: str
self, field: ForeignKeyField, parent_virtual: bool, nested: bool, rel_part: str
) -> bool:
prev_part_of_related = "__".join(rel_part.split("__")[:-1])
partial_match = any(
@ -112,39 +112,39 @@ class Query:
[x.startswith(rel_part) for x in (self.auto_related + self.already_checked)]
)
return (
(field.virtual and parent_virtual)
or (partial_match and not already_checked)
) or not nested
(field.virtual and parent_virtual)
or (partial_match and not already_checked)
) or not nested
def on_clause(
self, previous_alias: str, alias: str, from_clause: str, to_clause: str,
self, previous_alias: str, alias: str, from_clause: str, to_clause: str,
) -> text:
left_part = f"{alias}_{to_clause}"
right_part = f"{previous_alias + '_' if previous_alias else ''}{from_clause}"
return text(f"{left_part}={right_part}")
def _build_join_parameters(
self, part: str, join_params: JoinParameters
self, part: str, join_params: JoinParameters
) -> JoinParameters:
model_cls = join_params.model_cls.__model_fields__[part].to
to_table = model_cls.__table__.name
model_cls = join_params.model_cls.Meta.model_fields[part].to
to_table = model_cls.Meta.table.name
alias = model_cls._orm_relationship_manager.resolve_relation_join(
alias = model_cls.Meta._orm_relationship_manager.resolve_relation_join(
join_params.from_table, to_table
)
if alias not in self.used_aliases:
if join_params.prev_model.__model_fields__[part].virtual:
if join_params.prev_model.Meta.model_fields[part].virtual:
to_key = next(
(
v
for k, v in model_cls.__model_fields__.items()
if isinstance(v, ForeignKey) and v.to == join_params.prev_model
for k, v in model_cls.Meta.model_fields.items()
if issubclass(v, ForeignKeyField) and v.to == join_params.prev_model
),
None,
).name
from_key = model_cls.__pkname__
from_key = model_cls.Meta.pkname
else:
to_key = model_cls.__pkname__
to_key = model_cls.Meta.pkname
from_key = part
on_clause = self.on_clause(
@ -157,8 +157,8 @@ class Query:
self.select_from = sqlalchemy.sql.outerjoin(
self.select_from, target_table, on_clause
)
self.order_bys.append(text(f"{alias}_{to_table}.{model_cls.__pkname__}"))
self.columns.extend(self.prefixed_columns(alias, model_cls.__table__))
self.order_bys.append(text(f"{alias}_{to_table}.{model_cls.Meta.pkname}"))
self.columns.extend(self.prefixed_columns(alias, model_cls.Meta.table))
self.used_aliases.append(alias)
previous_alias = alias
@ -167,24 +167,28 @@ class Query:
return JoinParameters(prev_model, previous_alias, from_table, model_cls)
def _extract_auto_required_relations(
self,
prev_model: Type["Model"],
rel_part: str = "",
nested: bool = False,
parent_virtual: bool = False,
self,
prev_model: Type["Model"],
rel_part: str = "",
nested: bool = False,
parent_virtual: bool = False,
) -> None:
for field_name, field in prev_model.__model_fields__.items():
for field_name, field in prev_model.Meta.model_fields.items():
if self._field_is_a_foreign_key_and_no_circular_reference(
field, field_name, rel_part
field, field_name, rel_part
):
rel_part = field_name if not rel_part else rel_part + "__" + field_name
if not field.nullable:
print('add', rel_part, field)
if rel_part not in self._select_related:
self.auto_related.append("__".join(rel_part.split("__")[:-1]))
new_related = "__".join(rel_part.split("__")[:-1]) if len(
rel_part.split("__")) > 1 else rel_part
self.auto_related.append(new_related)
rel_part = ""
elif self._field_qualifies_to_deeper_search(
field, parent_virtual, nested, rel_part
field, parent_virtual, nested, rel_part
):
print('deeper', rel_part, field, field.to)
self._extract_auto_required_relations(
prev_model=field.to,
rel_part=rel_part,
@ -204,7 +208,7 @@ class Query:
self._select_related = new_joins + self.auto_related
def _apply_expression_modifiers(
self, expr: sqlalchemy.sql.select
self, expr: sqlalchemy.sql.select
) -> sqlalchemy.sql.select:
if self.filter_clauses:
if len(self.filter_clauses) == 1:

View File

@ -14,12 +14,12 @@ if TYPE_CHECKING: # pragma no cover
class QuerySet:
def __init__(
self,
model_cls: Type["Model"] = None,
filter_clauses: List = None,
select_related: List = None,
limit_count: int = None,
offset: int = None,
self,
model_cls: Type["Model"] = None,
filter_clauses: List = None,
select_related: List = None,
limit_count: int = None,
offset: int = None,
) -> None:
self.model_cls = model_cls
self.filter_clauses = [] if filter_clauses is None else filter_clauses
@ -33,11 +33,11 @@ class QuerySet:
@property
def database(self) -> databases.Database:
return self.model_cls.__database__
return self.model_cls.Meta.database
@property
def table(self) -> sqlalchemy.Table:
return self.model_cls.__table__
return self.model_cls.Meta.table
def build_select_expression(self) -> sqlalchemy.sql.select:
qry = Query(
@ -148,12 +148,12 @@ class QuerySet:
new_kwargs = dict(**kwargs)
# Remove primary key when None to prevent not null constraint in postgresql.
pkname = self.model_cls.__pkname__
pk = self.model_cls.__model_fields__[pkname]
pkname = self.model_cls.Meta.pkname
pk = self.model_cls.Meta.model_fields[pkname]
if (
pkname in new_kwargs
and new_kwargs.get(pkname) is None
and (pk.nullable or pk.autoincrement)
pkname in new_kwargs
and new_kwargs.get(pkname) is None
and (pk.nullable or pk.autoincrement)
):
del new_kwargs[pkname]
@ -163,11 +163,11 @@ class QuerySet:
if isinstance(new_kwargs.get(field), ormar.Model):
new_kwargs[field] = getattr(
new_kwargs.get(field),
self.model_cls.__model_fields__[field].to.__pkname__,
self.model_cls.Meta.model_fields[field].to.Meta.pkname,
)
else:
new_kwargs[field] = new_kwargs.get(field).get(
self.model_cls.__model_fields__[field].to.__pkname__
self.model_cls.Meta.model_fields[field].to.Meta.pkname
)
# Build the insert expression.
@ -176,5 +176,6 @@ class QuerySet:
# Execute the insert, and return a new model instance.
instance = self.model_cls(**kwargs)
instance.pk = await self.database.execute(expr)
pk = await self.database.execute(expr)
setattr(instance, self.model_cls.Meta.pkname, pk)
return instance