diff --git a/ogc/bblocks/metadata-schema.yaml b/ogc/bblocks/metadata-schema.yaml index 3dfa651..f92b025 100644 --- a/ogc/bblocks/metadata-schema.yaml +++ b/ogc/bblocks/metadata-schema.yaml @@ -161,3 +161,22 @@ properties: type: array items: type: string + extends: + description: | + Used to declare that the schema for this Building Block extends another one. The schemas will be joined + with an "allOf" declaration. + oneOf: + - description: Identifier of the base Building Block to extend + type: string + - type: object + required: + - itemIdentifier + properties: + itemIdentifier: + description: Identifier of the base Building Block to extend + type: string + path: + description: | + Property path where the schema for this Building Block will be inserted, in the format "a.b.c.d". + If a property is an array, "prop[]" notation can be used (e.g., "a.b[].c.d[]"). + type: string diff --git a/ogc/bblocks/util.py b/ogc/bblocks/util.py index 282ad27..e5282ab 100644 --- a/ogc/bblocks/util.py +++ b/ogc/bblocks/util.py @@ -10,6 +10,7 @@ from typing import Any, Sequence, Callable import jsonschema +import networkx as nx from ogc.na.annotate_schema import SchemaAnnotator, ContextBuilder from ogc.na.util import load_yaml, dump_yaml, is_url @@ -149,6 +150,7 @@ def jsonld_context_contents(self): self._lazy_properties['jsonld_context_contents'] = load_file(self.jsonld_context) return self._lazy_properties['jsonld_context_contents'] + class BuildingBlockRegister: def __init__(self, @@ -191,8 +193,11 @@ def __init__(self, print('=========', file=sys.stderr) if find_dependencies: + dep_graph = nx.DiGraph() + for bblock in self.bblocks.values(): found_deps = self.find_dependencies(bblock) + dep_graph.add_edges_from([(d, bblock.identifier) for d in found_deps]) deps = bblock.metadata.get('dependsOn') if isinstance(deps, str): found_deps.add(deps) @@ -200,6 +205,11 @@ def __init__(self, found_deps.update(deps) if found_deps: bblock.metadata['dependsOn'] = list(found_deps) + cycles = list(nx.simple_cycles(dep_graph)) + if cycles: + cycles_str = '\n - '.join(' -> '.join(reversed(c)) + ' -> ' + c[-1] for c in cycles) + raise BuildingBlockError(f"Circular dependencies found: \n - {cycles_str}") + self.bblocks = {b: self.bblocks[b] for b in nx.topological_sort(dep_graph) if b in self.bblocks} def find_dependencies(self, bblock: BuildingBlock) -> set[str]: if not bblock.schema.is_file(): @@ -230,6 +240,13 @@ def walk_schema(schema): walk_schema(bblock_schema) + extends = bblock.metadata.get('extends') + if extends: + if isinstance(extends, str): + deps.add(extends) + elif isinstance(extends, dict): + deps.add(extends['itemIdentifier']) + return deps diff --git a/requirements.txt b/requirements.txt index 4554c92..3485222 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ pyparsing~=3.0.9 pyshacl~=0.21.0 rdflib~=6.3.2 jsonref~=1.1.0 +networkx