1919from gel ._internal import _qb
2020from gel ._internal ._schemapath import (
2121 TypeNameIntersection ,
22+ TypeNameExpr ,
2223)
2324from gel ._internal import _type_expression
2425from 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+ )
2834from ._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
248254class 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+
259273T = TypeVar ('T' )
260274U = 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
320330def 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,99 @@ 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 (lhs_backlinks := getattr (lhs , "__backlinks__" , None )) and (
412+ rhs_backlinks := getattr (rhs , "__backlinks__" , None )
413+ ):
414+ backlinks_model = create_intersection_backlinks (
415+ lhs_backlinks ,
416+ rhs_backlinks ,
417+ result ,
418+ __gel_reflection__ .type_name ,
419+ )
420+ setattr ( # noqa: B010
421+ result ,
422+ "__backlinks__" ,
423+ GelObjectBacklinksModelDescriptor [backlinks_model ](), # type: ignore [valid-type]
424+ )
426425
427426 if lhs not in _type_intersection_cache :
428427 _type_intersection_cache [lhs ] = weakref .WeakKeyDictionary ()
429428 _type_intersection_cache [lhs ][rhs ] = result
430429
431430 return result
431+
432+
433+ def _order_base_types (lhs : type , rhs : type ) -> tuple [type , ...]:
434+ if lhs == rhs :
435+ return (lhs ,)
436+ elif issubclass (lhs , rhs ):
437+ return (lhs , rhs )
438+ elif issubclass (rhs , lhs ):
439+ return (rhs , lhs )
440+ else :
441+ return (lhs , rhs )
442+
443+
444+ def create_intersection_backlinks (
445+ lhs_backlinks : type [AbstractGelObjectBacklinksModel ],
446+ rhs_backlinks : type [AbstractGelObjectBacklinksModel ],
447+ result : type [BaseGelModelIntersection [Any , Any ]],
448+ result_type_name : TypeNameExpr ,
449+ ) -> type [AbstractGelObjectBacklinksModel ]:
450+ reflection = type (
451+ "__gel_reflection__" ,
452+ _order_base_types (
453+ lhs_backlinks .__gel_reflection__ ,
454+ rhs_backlinks .__gel_reflection__ ,
455+ ),
456+ {
457+ "name" : result_type_name ,
458+ "type_name" : result_type_name ,
459+ "pointers" : (
460+ lhs_backlinks .__gel_reflection__ .pointers
461+ | rhs_backlinks .__gel_reflection__ .pointers
462+ ),
463+ },
464+ )
465+
466+ # Generate field descriptors for backlinks.
467+ field_descriptors : dict [str , ModelFieldDescriptor ] = combine_dicts (
468+ {
469+ p_name : field_descriptor (result , p_name , path_alias .__gel_origin__ )
470+ for p_name in lhs_backlinks .__gel_reflection__ .pointers
471+ if (
472+ hasattr (lhs_backlinks , p_name )
473+ and (path_alias := getattr (lhs_backlinks , p_name , None ))
474+ is not None
475+ and isinstance (path_alias , _qb .PathAlias )
476+ )
477+ },
478+ {
479+ p_name : field_descriptor (result , p_name , path_alias .__gel_origin__ )
480+ for p_name in rhs_backlinks .__gel_reflection__ .pointers
481+ if (
482+ hasattr (rhs_backlinks , p_name )
483+ and (path_alias := getattr (rhs_backlinks , p_name , None ))
484+ is not None
485+ and isinstance (path_alias , _qb .PathAlias )
486+ )
487+ },
488+ )
489+
490+ backlinks = type (
491+ f"__{ result_type_name .name } _backlinks__" ,
492+ (BaseGelModelIntersectionBacklinks ,),
493+ {
494+ 'lhs' : lhs_backlinks ,
495+ 'rhs' : rhs_backlinks ,
496+ '__gel_reflection__' : reflection ,
497+ '__module__' : __name__ ,
498+ ** field_descriptors ,
499+ },
500+ )
501+
502+ return backlinks
0 commit comments