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,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