You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Protocols generated for a sync client can inherit an async base class. The generator switches a
relationship's peer to its *Sync variant whenever one exists, but for an inheritance list it only
switches the three names in CORE_BASE_CLASS_TO_SYNCIFY (CoreProfile, CoreObjectTemplate, CoreNode). Every other core kind in an inherit_from list keeps its async name, so the generated
sync class ends up with CoreNode in its MRO instead of CoreNodeSync.
The user-visible effect is that type checking a sync codebase fails on correct code.
Reproduction
infrahubctl protocols --sync against any schema whose nodes inherit a core generic, or a schema
with profiles or object templates. With the schema in tests/fixtures/schema_05.json the generated
sync file contains:
classIpamIPAddress(BuiltinIPAddress): # should be BuiltinIPAddressSyncclassIpamPrefix(BuiltinIPPrefix): # should be BuiltinIPPrefixSyncclassTemplateInfraDevice(LineageSource, CoreObjectTemplateSync, CoreNodeSync): # LineageSourceSyncclassProfileLocationSite(LineageSource, CoreProfileSync, CoreNodeSync): # LineageSourceSync
14 inheritance references are affected in that one fixture: BuiltinIPAddress and BuiltinIPPrefix once each, and LineageSource twelve times. Note the mixed bases on the last two,
where the same class correctly gets CoreProfileSync and CoreNodeSync alongside an async LineageSource.
Then type check code that uses one of them with a sync client:
frominfrahub_sdk.protocolsimportBuiltinIPAddressclassIpamIPAddress(BuiltinIPAddress): # what the generator emits into the sync filepassdeff(node: IpamIPAddress) ->None:
node.save()
$ uv run mypy repro.pyrepro.py:9: error: Value of type "Coroutine[Any, Any, None]" must be used [unused-coroutine]repro.py:9: note: Are you missing an await?
node.save resolves to CoreNode.save, which is async def, so a correct sync call is reported as
a missing await. Swap the base for BuiltinIPAddressSync and it type checks clean. Only typing is
affected; nothing changes at runtime, because these classes are never instantiated.
Root cause
infrahub_sdk/protocols_generator/generator.py, in _jinja2_filter_syncify. The list branch tests
membership of _inherited_sync_names, which for a user schema is CORE_BASE_CLASS_TO_SYNCIFY:
_jinja2_filter_render_relationship uses the other set, _peer_sync_names, which is every base
protocol that has a Sync counterpart. That is why peers are switched correctly and bases are not.
The two sets are deliberately separate today to preserve existing output; see the comment where they
are built.
Suggested fix
Use the _peer_sync_names rule for inheritance lists as well, so any base protocol with a *Sync
counterpart is switched. CORE_BASE_CLASS_TO_SYNCIFY is then only needed for its other job,
ordering the bases so the core ones come last, and the two name sets in CodeGenerator collapse
into one.
Worth checking while in there: whether ordering still needs its own list, and whether _inherited_sync_names can be deleted outright.
Notes for whoever picks this up
This changes the output of infrahubctl protocols, so tests/fixtures/protocols_generator/user_schema_sync.txt
will move. That fixture is a full-file golden, so its diff is the review surface: expect exactly
the 14 references above to change and nothing else. user_schema_async.txt must not change at all.
Users regenerating after the fix may see new type errors in their own code, since a sync class that
previously exposed async save/delete/create will start exposing the sync ones. That is the
point of the fix, but it belongs in the changelog as a fixed entry with that consequence spelled
out.
Summary
Protocols generated for a sync client can inherit an async base class. The generator switches a
relationship's peer to its
*Syncvariant whenever one exists, but for an inheritance list it onlyswitches the three names in
CORE_BASE_CLASS_TO_SYNCIFY(CoreProfile,CoreObjectTemplate,CoreNode). Every other core kind in aninherit_fromlist keeps its async name, so the generatedsync class ends up with
CoreNodein its MRO instead ofCoreNodeSync.The user-visible effect is that type checking a sync codebase fails on correct code.
Reproduction
infrahubctl protocols --syncagainst any schema whose nodes inherit a core generic, or a schemawith profiles or object templates. With the schema in
tests/fixtures/schema_05.jsonthe generatedsync file contains:
14 inheritance references are affected in that one fixture:
BuiltinIPAddressandBuiltinIPPrefixonce each, andLineageSourcetwelve times. Note the mixed bases on the last two,where the same class correctly gets
CoreProfileSyncandCoreNodeSyncalongside an asyncLineageSource.Then type check code that uses one of them with a sync client:
node.saveresolves toCoreNode.save, which isasync def, so a correct sync call is reported asa missing
await. Swap the base forBuiltinIPAddressSyncand it type checks clean. Only typing isaffected; nothing changes at runtime, because these classes are never instantiated.
Root cause
infrahub_sdk/protocols_generator/generator.py, in_jinja2_filter_syncify. The list branch testsmembership of
_inherited_sync_names, which for a user schema isCORE_BASE_CLASS_TO_SYNCIFY:_jinja2_filter_render_relationshipuses the other set,_peer_sync_names, which is every baseprotocol that has a
Synccounterpart. That is why peers are switched correctly and bases are not.The two sets are deliberately separate today to preserve existing output; see the comment where they
are built.
Suggested fix
Use the
_peer_sync_namesrule for inheritance lists as well, so any base protocol with a*Synccounterpart is switched.
CORE_BASE_CLASS_TO_SYNCIFYis then only needed for its other job,ordering the bases so the core ones come last, and the two name sets in
CodeGeneratorcollapseinto one.
Worth checking while in there: whether ordering still needs its own list, and whether
_inherited_sync_namescan be deleted outright.Notes for whoever picks this up
infrahubctl protocols, sotests/fixtures/protocols_generator/user_schema_sync.txtwill move. That fixture is a full-file golden, so its diff is the review surface: expect exactly
the 14 references above to change and nothing else.
user_schema_async.txtmust not change at all.previously exposed async
save/delete/createwill start exposing the sync ones. That is thepoint of the fix, but it belongs in the changelog as a
fixedentry with that consequence spelledout.
which had to guarantee byte-identical output for user schemas.