Fix type reference order in code generated by gen-python #181

Merged
christian-monch merged 4 commits from fix-subclass-id-error into master 2025-12-17 22:16:55 +00:00
7 changed files with 100 additions and 1 deletions

View file

@ -1,3 +1,11 @@
# 5.3.4 (2025-12-17)
## Bugfixes
- add a patch to fix the order of type declarations that are generated by
linkml.generators.pythongen
# 5.3.3 (2025-12-16)
## Bugfixes

View file

@ -1 +1 @@
__version__ = '5.3.3'
__version__ = '5.3.4'

View file

@ -2,5 +2,6 @@
import dump_things_service.patches.compile
import dump_things_service.patches.enumerations
import dump_things_service.patches.ifabsent_processing
import dump_things_service.patches.pythongen_gen_references
import dump_things_service.patches.rdflib_loader
import dump_things_service.patches.yamlutils

View file

@ -0,0 +1,49 @@
import logging
from collections import defaultdict
from graphlib import TopologicalSorter
from importlib import import_module
from linkml_runtime.utils.formatutils import camelcase
logger = logging.getLogger('dump_things_service')
def patched_gen_references(self) -> str:
"""Generate python type declarations for all identifiers (primary keys)"""
rval = dict()
graph = defaultdict(set)
for cls in self._sort_classes(self.schema.classes.values()):
if not cls.imported_from:
pkeys = self.primary_keys_for(cls)
if pkeys:
for pk in pkeys:
classname = camelcase(cls.name) + camelcase(self.aliased_slot_name(pk))
# If we've got a parent slot and the range of the parent is the range of the child, the
# child slot is a subclass of the parent. Otherwise, the child range has been overridden,
# so the inheritance chain has been broken
parent_pk = self.class_identifier(cls.is_a) if cls.is_a else None
parent_pk_slot = self.schema.slots[parent_pk] if parent_pk else None
pk_slot = self.schema.slots[pk]
if parent_pk_slot and (parent_pk_slot.name == pk or pk_slot.range == parent_pk_slot.range):
parents = self.class_identifier_path(cls.is_a, False)
else:
parents = self.slot_range_path(pk_slot)
parent_cls = (
"extended_" + parents[-1] if parents[-1] in ["str", "float", "int"] else parents[-1]
)
rval[classname] = f"class {classname}({parent_cls}):\n\tpass"
graph[classname].add(parent_cls)
break # We only do the first primary key
return "\n\n\n".join(
rval[name]
for name in TopologicalSorter(graph).static_order()
if name in rval
)
logger.info('patching linkml.generators.pythongen.PythonGenerator.gen_references')
cls = import_module('linkml.generators.pythongen')
cls.PythonGenerator.gen_references = patched_gen_references

View file

@ -0,0 +1,33 @@
id: https://example.org/subclass-id-error-trigger
name: subclass-error-trigger
version: UNRELEASED
title: subclass ID error trigger
prefixes:
linkml: https://w3id.org/linkml/
imports:
- linkml:types
slots:
pid:
identifier: true
range: uriorcurie
required: true
annotation_tag:
description: A tag identifying an annotation.
range: Thing
classes:
Annotation:
slots:
- annotation_tag
slot_usage:
annotation_tag:
key: true
Thing:
slots:
- pid

View file

@ -318,6 +318,10 @@ def _check_result_json(
assert record['relations'][linked_pid] == {'pid': linked_pid}
# We skip this test because the dlflatsocial-schema has changed. Person is now
# derived from FlatThing and FlatThing.relations has range FlatThing.
# That breaks the tests. They assume that Person.relations has range Thing.
@pytest.mark.xfail
def test_dont_extract_empty_things_on_service(fastapi_client_simple):
test_client, store = fastapi_client_simple

View file

@ -17,3 +17,7 @@ def test_gen_pydantic():
def test_gen_python():
get_schema_model_for_schema(str(schema_dir / 'schema-merged.yaml'))
def test_subclass_id():
get_schema_model_for_schema(str(schema_dir / 'schema-subclass-id-error.yaml'))