@@ -435,6 +435,12 @@ class ModuleAspect(enum.Enum):
435435 LATE = enum .auto ()
436436
437437
438+ @dataclasses .dataclass (frozen = True , kw_only = True )
439+ class Backlink :
440+ source : reflection .ObjectType
441+ pointer : reflection .Pointer
442+
443+
438444class SchemaGenerator :
439445 def __init__ (
440446 self ,
@@ -451,6 +457,9 @@ def __init__(
451457 self ._std_modules : list [SchemaPath ] = []
452458 self ._types : Mapping [str , reflection .Type ] = {}
453459 self ._casts : reflection .CastMatrix
460+ self ._backlinks : dict [
461+ reflection .ObjectType , dict [str , list [Backlink ]]
462+ ] = {}
454463 self ._operators : reflection .OperatorMatrix
455464 self ._functions : list [reflection .Function ]
456465 self ._globals : list [reflection .Global ]
@@ -579,6 +588,7 @@ def run(self, outdir: pathlib.Path) -> tuple[Schema, set[pathlib.Path]]:
579588 all_casts = self ._casts ,
580589 all_operators = self ._operators ,
581590 all_globals = self ._globals ,
591+ all_backlinks = self ._backlinks ,
582592 modules = self ._modules ,
583593 schema_part = self ._schema_part ,
584594 )
@@ -604,6 +614,7 @@ def run(self, outdir: pathlib.Path) -> tuple[Schema, set[pathlib.Path]]:
604614 all_casts = self ._casts ,
605615 all_operators = self ._operators ,
606616 all_globals = self ._globals ,
617+ all_backlinks = self ._backlinks ,
607618 modules = all_modules ,
608619 schema_part = self ._schema_part ,
609620 )
@@ -659,6 +670,24 @@ def introspect_schema(self) -> Schema:
659670 if reflection .is_object_type (t ):
660671 name = t .schemapath
661672 self ._modules [name .parent ]["object_types" ][name .name ] = t
673+
674+ for p in t .pointers :
675+ if (
676+ reflection .is_link (p )
677+ # For now don't include std::BaseObject.__type__
678+ # Users should just select on the appropriate type
679+ and p .name != "__type__"
680+ ):
681+ target = self ._types [p .target_id ]
682+ assert isinstance (target , reflection .ObjectType )
683+ if target not in self ._backlinks :
684+ self ._backlinks [target ] = {}
685+ if p .name not in self ._backlinks [target ]:
686+ self ._backlinks [target ][p .name ] = []
687+ self ._backlinks [target ][p .name ].append (
688+ Backlink (source = t , pointer = p )
689+ )
690+
662691 elif reflection .is_scalar_type (t ):
663692 name = t .schemapath
664693 self ._modules [name .parent ]["scalar_types" ][name .name ] = t
@@ -694,6 +723,7 @@ def _generate_common_types(
694723 all_casts = self ._casts ,
695724 all_operators = self ._operators ,
696725 all_globals = self ._globals ,
726+ all_backlinks = self ._backlinks ,
697727 modules = self ._modules ,
698728 schema_part = self ._schema_part ,
699729 )
@@ -944,6 +974,9 @@ def __init__(
944974 all_casts : reflection .CastMatrix ,
945975 all_operators : reflection .OperatorMatrix ,
946976 all_globals : list [reflection .Global ],
977+ all_backlinks : Mapping [
978+ reflection .ObjectType , Mapping [str , Sequence [Backlink ]]
979+ ],
947980 modules : Collection [SchemaPath ],
948981 schema_part : reflection .SchemaPart ,
949982 ) -> None :
@@ -954,6 +987,7 @@ def __init__(
954987 self ._casts = all_casts
955988 self ._operators = all_operators
956989 self ._globals = all_globals
990+ self ._backlinks = all_backlinks
957991 schema_obj_type = None
958992 for t in all_types .values ():
959993 self ._types_by_name [t .name ] = t
@@ -4086,6 +4120,9 @@ def _mangle_default_shape(name: str) -> str:
40864120 if proplinks :
40874121 self .write_object_type_link_models (objtype )
40884122
4123+ if name != "std::FreeObject" :
4124+ self ._write_object_backlinks (objtype )
4125+
40894126 anyobject_meta = self .get_object (
40904127 SchemaPath ("std" , "__anyobject_meta__" ),
40914128 aspect = ModuleAspect .SHAPES ,
@@ -4213,6 +4250,16 @@ def write_id_computed(
42134250 self .write (f"__gel_type_class__ = __{ name } _ops__" )
42144251 if objtype .name == "std::BaseObject" :
42154252 write_id_attr (objtype , "RequiredId" )
4253+
4254+ if name != "std::FreeObject" :
4255+ backlinks_model_name = self ._mangle_backlinks_model_name (name )
4256+ g_oblm_desc = self .import_name (
4257+ BASE_IMPL , "GelObjectBacklinksModelDescriptor"
4258+ )
4259+ oblm_desc = f"{ g_oblm_desc } [{ backlinks_model_name } ]"
4260+ self .write (f"__backlinks__: { oblm_desc } = { oblm_desc } ()" )
4261+ self .write ()
4262+
42164263 self ._write_base_object_type_body (objtype , include_tname = True )
42174264 with self .type_checking ():
42184265 self ._write_object_type_qb_methods (objtype )
@@ -4333,6 +4380,195 @@ def write_id_computed(
43334380
43344381 self .write ()
43354382
4383+ @staticmethod
4384+ def _mangle_backlinks_model_name (name : str ) -> str :
4385+ return f"__{ name } _backlinks__"
4386+
4387+ def _write_object_backlinks (
4388+ self ,
4389+ objtype : reflection .ObjectType ,
4390+ ) -> None :
4391+ type_name = objtype .schemapath
4392+ name = type_name .name
4393+
4394+ schema_path = self .import_name (BASE_IMPL , "SchemaPath" )
4395+ parametric_type_name = self .import_name (
4396+ BASE_IMPL , "ParametricTypeName"
4397+ )
4398+ computed_multi_link = self .import_name (BASE_IMPL , "ComputedMultiLink" )
4399+ std_base_object_t = self .get_object (
4400+ SchemaPath .from_segments ("std" , "BaseObject" ),
4401+ aspect = ModuleAspect .SHAPES ,
4402+ )
4403+
4404+ backlinks_model_name = self ._mangle_backlinks_model_name (name )
4405+
4406+ base_object_types = [
4407+ base_type
4408+ for base_ref in objtype .bases
4409+ if (base_type := self ._types .get (base_ref .id , None ))
4410+ if isinstance (base_type , reflection .ObjectType )
4411+ ]
4412+
4413+ backlinks_base_types : list [str ]
4414+ base_backlinks_reflections : list [str ]
4415+ base_backlinks_models : list [str ]
4416+
4417+ if objtype .name == "std::BaseObject" :
4418+ object_backlinks_model = self .import_name (
4419+ BASE_IMPL , "GelObjectBacklinksModel"
4420+ )
4421+ backlinks_base_types = [object_backlinks_model ]
4422+ base_backlinks_reflections = [
4423+ f"{ object_backlinks_model } .__gel_reflection__"
4424+ ]
4425+ base_backlinks_models = []
4426+ else :
4427+ backlinks_base_types = [
4428+ self .get_object (
4429+ SchemaPath (
4430+ base_type .schemapath .parent ,
4431+ self ._mangle_backlinks_model_name (
4432+ base_type .schemapath .name
4433+ ),
4434+ ),
4435+ aspect = ModuleAspect .SHAPES ,
4436+ )
4437+ for base_type in base_object_types
4438+ ]
4439+ base_backlinks_reflections = [
4440+ f"{ bbt } .__gel_reflection__" for bbt in backlinks_base_types
4441+ ]
4442+ base_backlinks_models = backlinks_base_types
4443+
4444+ object_backlinks = self ._backlinks .get (objtype , {})
4445+
4446+ # The backlinks model class
4447+ with self ._class_def (backlinks_model_name , backlinks_base_types ):
4448+ with self ._class_def (
4449+ "__gel_reflection__" , base_backlinks_reflections
4450+ ):
4451+ obj_name = type_name .as_python_code (
4452+ schema_path , parametric_type_name
4453+ )
4454+ self .write (f"name = { obj_name } " )
4455+ self .write (f"type_name = { obj_name } " )
4456+ self ._write_backlinks_pointers_reflection (
4457+ object_backlinks , base_backlinks_models
4458+ )
4459+
4460+ for backlink_name in object_backlinks :
4461+ backlink_t = f"{ computed_multi_link } [{ std_base_object_t } ]"
4462+ self .write (f"{ backlink_name } : { backlink_t } " )
4463+
4464+ self .export (backlinks_model_name )
4465+
4466+ self .write ()
4467+ self .write ()
4468+
4469+ def _write_backlinks_pointers_reflection (
4470+ self ,
4471+ object_backlinks : Mapping [str , Sequence [Backlink ]],
4472+ base_backlinks_models : Sequence [str ],
4473+ ) -> None :
4474+ dict_ = self .import_name (
4475+ "builtins" , "dict" , import_time = ImportTime .typecheck
4476+ )
4477+ str_ = self .import_name (
4478+ "builtins" , "str" , import_time = ImportTime .typecheck
4479+ )
4480+ gel_ptr_ref = self .import_name (
4481+ BASE_IMPL ,
4482+ "GelPointerReflection" ,
4483+ import_time = ImportTime .runtime
4484+ if object_backlinks
4485+ else ImportTime .typecheck ,
4486+ )
4487+ lazyclassproperty = self .import_name (BASE_IMPL , "LazyClassProperty" )
4488+ ptr_ref_t = f"{ dict_ } [{ str_ } , { gel_ptr_ref } ]"
4489+ with self ._classmethod_def (
4490+ "pointers" ,
4491+ [],
4492+ ptr_ref_t ,
4493+ decorators = (f'{ lazyclassproperty } ["{ ptr_ref_t } "]' ,),
4494+ ):
4495+ if object_backlinks :
4496+ self .write (f"my_ptrs: { ptr_ref_t } = {{" )
4497+ classes = {
4498+ "SchemaPath" : self .import_name (BASE_IMPL , "SchemaPath" ),
4499+ "ParametricTypeName" : self .import_name (
4500+ BASE_IMPL , "ParametricTypeName"
4501+ ),
4502+ "GelPointerReflection" : gel_ptr_ref ,
4503+ "Cardinality" : self .import_name (BASE_IMPL , "Cardinality" ),
4504+ "PointerKind" : self .import_name (BASE_IMPL , "PointerKind" ),
4505+ "StdBaseObject" : self .get_object (
4506+ SchemaPath .from_segments ("std" , "BaseObject" ),
4507+ aspect = ModuleAspect .SHAPES ,
4508+ ),
4509+ }
4510+ with self .indented ():
4511+ for (
4512+ backlink_name ,
4513+ backlink_values ,
4514+ ) in object_backlinks .items ():
4515+ r = self ._reflect_backlink (
4516+ backlink_name , backlink_values , classes
4517+ )
4518+ self .write (f"{ backlink_name !r} : { r } ," )
4519+ self .write ("}" )
4520+ else :
4521+ self .write (f"my_ptrs: { ptr_ref_t } = {{}}" )
4522+
4523+ if base_backlinks_models :
4524+ pp = "__gel_reflection__.pointers"
4525+ ret = self .format_list (
4526+ "return ({list})" ,
4527+ [
4528+ "my_ptrs" ,
4529+ * _map_name (
4530+ lambda s : f"{ s } .{ pp } " , base_backlinks_models
4531+ ),
4532+ ],
4533+ separator = " | " ,
4534+ carry_separator = True ,
4535+ )
4536+ else :
4537+ ret = "return my_ptrs"
4538+
4539+ self .write (ret )
4540+
4541+ self .write ()
4542+
4543+ def _reflect_backlink (
4544+ self ,
4545+ name : str ,
4546+ backlinks : Sequence [Backlink ],
4547+ classes : dict [str , str ],
4548+ ) -> str :
4549+ kwargs : dict [str , str ] = {
4550+ "name" : repr (name ),
4551+ "type" : classes ["StdBaseObject" ],
4552+ "kind" : (
4553+ f"{ classes ['PointerKind' ]} ({ str (reflection .PointerKind .Link )!r} )"
4554+ ),
4555+ "cardinality" : (
4556+ f"{ classes ['Cardinality' ]} ({ str (reflection .Cardinality .Many )!r} )"
4557+ ),
4558+ "computed" : "True" ,
4559+ "readonly" : "True" ,
4560+ "has_default" : "False" ,
4561+ "mutable" : "False" ,
4562+ }
4563+
4564+ # For now don't get any back link props
4565+ kwargs ["properties" ] = "None"
4566+
4567+ return self .format_list (
4568+ f"{ classes ['GelPointerReflection' ]} ({{list}})" ,
4569+ [f"{ k } ={ v } " for k , v in kwargs .items ()],
4570+ )
4571+
43364572 @contextlib .contextmanager
43374573 def _object_type_variant (
43384574 self ,
0 commit comments