Skip to content

Commit 61a111d

Browse files
committed
Fix backlinks on intersections.
1 parent 76ebdf9 commit 61a111d

File tree

1 file changed

+118
-49
lines changed

1 file changed

+118
-49
lines changed

gel/_internal/_qbmodel/_abstract/_methods.py

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
from gel._internal import _qb
2020
from gel._internal._schemapath import (
2121
TypeNameIntersection,
22+
TypeNameExpr,
2223
)
2324
from gel._internal import _type_expression
2425
from gel._internal._xmethod import classonlymethod
2526

26-
from ._base import AbstractGelModel
27+
from ._base import AbstractGelModel, AbstractGelObjectBacklinksModel
2728

29+
from ._descriptors import (
30+
GelObjectBacklinksModelDescriptor,
31+
ModelFieldDescriptor,
32+
field_descriptor,
33+
)
2834
from ._expressions import (
2935
add_filter,
3036
add_limit,
@@ -241,8 +247,8 @@ def __edgeql_qb_expr__(cls) -> _qb.Expr: # pyright: ignore [reportIncompatibleM
241247
return _qb.SchemaSet(type_=this_type)
242248

243249

244-
_T_Lhs = TypeVar("_T_Lhs", bound="type[AbstractGelModel]")
245-
_T_Rhs = TypeVar("_T_Rhs", bound="type[AbstractGelModel]")
250+
_T_Lhs = TypeVar("_T_Lhs", bound="AbstractGelModel")
251+
_T_Rhs = TypeVar("_T_Rhs", bound="AbstractGelModel")
246252

247253

248254
class BaseGelModelIntersection(
@@ -256,6 +262,14 @@ class BaseGelModelIntersection(
256262
rhs: ClassVar[type[AbstractGelModel]]
257263

258264

265+
class BaseGelModelIntersectionBacklinks(
266+
AbstractGelObjectBacklinksModel,
267+
_type_expression.Intersection,
268+
):
269+
lhs: ClassVar[type[AbstractGelObjectBacklinksModel]]
270+
rhs: ClassVar[type[AbstractGelObjectBacklinksModel]]
271+
272+
259273
T = TypeVar('T')
260274
U = TypeVar('U')
261275

@@ -308,18 +322,14 @@ def combine_dicts(
308322
type[AbstractGelModel],
309323
weakref.WeakKeyDictionary[
310324
type[AbstractGelModel],
311-
type[
312-
BaseGelModelIntersection[
313-
type[AbstractGelModel], type[AbstractGelModel]
314-
]
315-
],
325+
type[BaseGelModelIntersection[AbstractGelModel, AbstractGelModel]],
316326
],
317327
] = weakref.WeakKeyDictionary()
318328

319329

320330
def create_intersection(
321-
lhs: _T_Lhs,
322-
rhs: _T_Rhs,
331+
lhs: type[_T_Lhs],
332+
rhs: type[_T_Rhs],
323333
) -> type[BaseGelModelIntersection[_T_Lhs, _T_Rhs]]:
324334
"""Create a runtime intersection type which acts like a GelModel."""
325335

@@ -347,7 +357,6 @@ class __gel_reflection__(_qb.GelObjectTypeExprMetadata.__gel_reflection__): # n
347357
rhs.__gel_reflection__.type_name,
348358
)
349359
)
350-
351360
pointers = ptr_reflections
352361

353362
@classmethod
@@ -358,52 +367,26 @@ def object(
358367
"Type expressions schema objects are inaccessible"
359368
)
360369

370+
# Create the resulting intersection type
361371
result = type(
362372
f"({lhs.__name__} & {rhs.__name__})",
363373
(BaseGelModelIntersection,),
364374
{
365375
'lhs': lhs,
366376
'rhs': rhs,
367377
'__gel_reflection__': __gel_reflection__,
378+
"__gel_proxied_dunders__": frozenset(
379+
{
380+
"__backlinks__",
381+
}
382+
),
368383
},
369384
)
370385

371-
# Generate path aliases for pointers.
372-
#
373-
# These are used to generate the appropriate path prefix when getting
374-
# pointers in shapes.
375-
#
376-
# For example, doing `Foo.select(foo=lambda x: x.is_(Bar).bar)`
377-
# will produce the query:
378-
# select Foo { [is Bar].bar }
379-
lhs_prefix = _qb.PathTypeIntersectionPrefix(
380-
type_=__gel_reflection__.type_name,
381-
type_filter=lhs.__gel_reflection__.type_name,
382-
)
383-
rhs_prefix = _qb.PathTypeIntersectionPrefix(
384-
type_=__gel_reflection__.type_name,
385-
type_filter=rhs.__gel_reflection__.type_name,
386-
)
387-
388-
def process_path_alias(
389-
p_name: str,
390-
p_refl: _qb.GelPointerReflection,
391-
path_alias: _qb.PathAlias,
392-
source: _qb.Expr,
393-
) -> _qb.PathAlias:
394-
return _qb.PathAlias(
395-
path_alias.__gel_origin__,
396-
_qb.Path(
397-
type_=p_refl.type,
398-
source=source,
399-
name=p_name,
400-
is_lprop=False,
401-
),
402-
)
403-
404-
path_aliases: dict[str, _qb.PathAlias] = combine_dicts(
386+
# Generate field descriptors.
387+
descriptors: dict[str, ModelFieldDescriptor] = combine_dicts(
405388
{
406-
p_name: process_path_alias(p_name, p_refl, path_alias, lhs_prefix)
389+
p_name: field_descriptor(result, p_name, path_alias.__gel_origin__)
407390
for p_name, p_refl in lhs.__gel_reflection__.pointers.items()
408391
if (
409392
hasattr(lhs, p_name)
@@ -412,7 +395,7 @@ def process_path_alias(
412395
)
413396
},
414397
{
415-
p_name: process_path_alias(p_name, p_refl, path_alias, rhs_prefix)
398+
p_name: field_descriptor(result, p_name, path_alias.__gel_origin__)
416399
for p_name, p_refl in rhs.__gel_reflection__.pointers.items()
417400
if (
418401
hasattr(rhs, p_name)
@@ -421,11 +404,97 @@ def process_path_alias(
421404
)
422405
},
423406
)
424-
for p_name, path_alias in path_aliases.items():
425-
setattr(result, p_name, path_alias)
407+
for p_name, descriptor in descriptors.items():
408+
setattr(result, p_name, descriptor)
409+
410+
# Generate backlinks if required (they should generally be)
411+
if hasattr(lhs, "__backlinks__") and hasattr(rhs, "__backlinks__"):
412+
backlinks_model = create_intersection_backlinks(
413+
lhs.__backlinks__,
414+
rhs.__backlinks__,
415+
result,
416+
__gel_reflection__.type_name,
417+
)
418+
setattr( # noqa: B010
419+
result,
420+
"__backlinks__",
421+
GelObjectBacklinksModelDescriptor[backlinks_model](),
422+
)
426423

427424
if lhs not in _type_intersection_cache:
428425
_type_intersection_cache[lhs] = weakref.WeakKeyDictionary()
429426
_type_intersection_cache[lhs][rhs] = result
430427

431428
return result
429+
430+
431+
def _order_base_types(lhs: type, rhs: type) -> tuple[type, ...]:
432+
if lhs == rhs:
433+
return (lhs,)
434+
elif issubclass(lhs, rhs):
435+
return (lhs, rhs)
436+
elif issubclass(rhs, lhs):
437+
return (rhs, lhs)
438+
else:
439+
return (lhs, rhs)
440+
441+
442+
def create_intersection_backlinks(
443+
lhs_backlinks: type[AbstractGelObjectBacklinksModel],
444+
rhs_backlinks: type[AbstractGelObjectBacklinksModel],
445+
result: type[BaseGelModelIntersection],
446+
result_type_name: TypeNameExpr,
447+
) -> type[AbstractGelObjectBacklinksModel]:
448+
reflection = type(
449+
"__gel_reflection__",
450+
_order_base_types(
451+
lhs_backlinks.__gel_reflection__,
452+
rhs_backlinks.__gel_reflection__,
453+
),
454+
{
455+
"name": result_type_name,
456+
"type_name": result_type_name,
457+
"pointers": (
458+
lhs_backlinks.__gel_reflection__.pointers
459+
| rhs_backlinks.__gel_reflection__.pointers
460+
),
461+
},
462+
)
463+
464+
# Generate field descriptors for backlinks.
465+
field_descriptors: dict[str, ModelFieldDescriptor] = combine_dicts(
466+
{
467+
p_name: field_descriptor(result, p_name, path_alias.__gel_origin__)
468+
for p_name in lhs_backlinks.__gel_reflection__.pointers
469+
if (
470+
hasattr(lhs_backlinks, p_name)
471+
and (path_alias := getattr(lhs_backlinks, p_name, None))
472+
is not None
473+
and isinstance(path_alias, _qb.PathAlias)
474+
)
475+
},
476+
{
477+
p_name: field_descriptor(result, p_name, path_alias.__gel_origin__)
478+
for p_name in rhs_backlinks.__gel_reflection__.pointers
479+
if (
480+
hasattr(rhs_backlinks, p_name)
481+
and (path_alias := getattr(rhs_backlinks, p_name, None))
482+
is not None
483+
and isinstance(path_alias, _qb.PathAlias)
484+
)
485+
},
486+
)
487+
488+
backlinks = type(
489+
f"__{result_type_name.name}_backlinks__",
490+
(BaseGelModelIntersectionBacklinks,),
491+
{
492+
'lhs': lhs_backlinks,
493+
'rhs': rhs_backlinks,
494+
'__gel_reflection__': reflection,
495+
'__module__': __name__,
496+
**field_descriptors,
497+
},
498+
)
499+
500+
return backlinks

0 commit comments

Comments
 (0)