Work begins on proper one to many support.
This commit is contained in:
parent
811b534b89
commit
ffa49f13e9
3 changed files with 82 additions and 24 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List, Tuple, Set, Dict, Optional
|
||||
from typing import List, Tuple, Set, Dict, Optional, Iterable
|
||||
from sqlalchemy import asc, desc
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import aliased, selectinload
|
||||
|
|
@ -20,10 +20,14 @@ class CRUDSpec:
|
|||
self.params = params
|
||||
self.root_alias = root_alias
|
||||
self.eager_paths: Set[Tuple[str, ...]] = set()
|
||||
# (parent_alias. relationship_attr, alias_for_target)
|
||||
self.join_paths: List[Tuple[object, InstrumentedAttribute, object]] = []
|
||||
self.alias_map: Dict[Tuple[str, ...], object] = {}
|
||||
self._root_fields: List[InstrumentedAttribute] = []
|
||||
self._rel_field_names: Dict[Tuple[str, ...], object] = {}
|
||||
# dotted non-collection fields (MANYTOONE etc)
|
||||
self._rel_field_names: Dict[Tuple[str, ...], List[str]] = {}
|
||||
# dotted collection fields (ONETOMANY)
|
||||
self._collection_field_names: Dict[str, List[str]] = {}
|
||||
self.include_paths: Set[Tuple[str, ...]] = set()
|
||||
|
||||
def _resolve_column(self, path: str):
|
||||
|
|
@ -117,11 +121,12 @@ class CRUDSpec:
|
|||
Parse ?fields=colA,colB,rel1.colC,rel1.rel2.colD
|
||||
- Root fields become InstrumentedAttributes bound to root_alias.
|
||||
- Related fields store attribute NAMES; we'll resolve them on the target class when building loader options.
|
||||
Returns (root_fields, rel_field_names).
|
||||
- Collection (uselist=True) relationships record child names by relationship key.
|
||||
Returns (root_fields, rel_field_names, root_field_names, collection_field_names_by_rel).
|
||||
"""
|
||||
raw = self.params.get('fields')
|
||||
if not raw:
|
||||
return [], {}, {}
|
||||
return [], {}, {}, {}
|
||||
|
||||
if isinstance(raw, list):
|
||||
tokens = []
|
||||
|
|
@ -133,14 +138,36 @@ class CRUDSpec:
|
|||
root_fields: List[InstrumentedAttribute] = []
|
||||
root_field_names: list[str] = []
|
||||
rel_field_names: Dict[Tuple[str, ...], List[str]] = {}
|
||||
collection_field_names: Dict[str, List[str]] = {}
|
||||
|
||||
for token in tokens:
|
||||
col, join_path = self._resolve_column(token)
|
||||
if not col:
|
||||
continue
|
||||
if join_path:
|
||||
rel_field_names.setdefault(join_path, []).append(col.key)
|
||||
self.eager_paths.add(join_path)
|
||||
# rel_field_names.setdefault(join_path, []).append(col.key)
|
||||
# self.eager_paths.add(join_path)
|
||||
try:
|
||||
cur_cls = self.model
|
||||
names = list(join_path)
|
||||
last_name = names[-1]
|
||||
for nm in names:
|
||||
rel_attr = getattr(cur_cls, nm)
|
||||
cur_cls = rel_attr.property.mapper.class_
|
||||
is_collection = bool(getattr(getattr(self.model, last_name), "property", None) and getattr(getattr(self.model, last_name).property, "uselist", False))
|
||||
except Exception:
|
||||
# Fallback: inspect the InstrumentedAttribute we recorded on join_paths
|
||||
is_collection = False
|
||||
for _pa, rel_attr, _al in self.join_paths:
|
||||
if rel_attr.key == (join_path[-1] if join_path else ""):
|
||||
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
|
||||
break
|
||||
|
||||
if is_collection:
|
||||
collection_field_names.setdefault(join_path[-1], []).append(col.key)
|
||||
else:
|
||||
rel_field_names.setdefault(join_path, []).append(col.key)
|
||||
self.eager_paths.add(join_path)
|
||||
else:
|
||||
root_fields.append(col)
|
||||
root_field_names.append(getattr(col, "key", token))
|
||||
|
|
@ -153,7 +180,11 @@ class CRUDSpec:
|
|||
|
||||
self._root_fields = root_fields
|
||||
self._rel_field_names = rel_field_names
|
||||
return root_fields, rel_field_names, root_field_names
|
||||
# return root_fields, rel_field_names, root_field_names
|
||||
for r, names in collection_field_names.items():
|
||||
seen3 = set()
|
||||
collection_field_names[r] = [n for n in names if not (n in seen3 or seen3.add(n))]
|
||||
return root_field_names, rel_field_names, root_field_names, collection_field_names
|
||||
|
||||
def get_eager_loads(self, root_alias, *, fields_map=None):
|
||||
loads = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue