Skip to content

Commit d9ddc99

Browse files
committed
Add TransformContext to TransformMetadata
Enriches all transform types (Python, Node, plugin) with a TransformContext carrying bblock metadata, example/snippet data, file paths, and register configuration. Subprocess transforms receive a serialized copy via CLI arg (Python) or injected JSON (Node/plugin), deserialized to a SimpleNamespace for consistent attribute access across all transform types.
1 parent 004ff72 commit d9ddc99

7 files changed

Lines changed: 108 additions & 13 deletions

File tree

ogc/bblocks/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,37 @@ def get_url(self, path: str | Path) -> str:
792792
return f"{self.base_url}{os.path.relpath(Path(path).resolve(), self._cwd)}"
793793

794794

795+
@dataclasses.dataclass
796+
class TransformContext:
797+
bblock_id: str
798+
bblock_name: str | None
799+
bblock_version: str | None
800+
bblock_tags: list
801+
bblock_files_path: str | None
802+
bblock_annotated_path: str | None
803+
bblock_metadata: dict
804+
example_index: int
805+
example: dict
806+
snippet_index: int
807+
snippet: dict
808+
output_file: str | None
809+
output_dir: str | None
810+
working_dir: str
811+
source_schema_path: str | None = None
812+
annotated_schema_path: str | None = None
813+
jsonld_context_path: str | None = None
814+
shacl_shapes_paths: list = dataclasses.field(default_factory=list)
815+
base_url: str | None = None
816+
github_base_url: str | None = None
817+
git_repository: str | None = None
818+
id_prefix: str = ''
819+
imported_register_urls: list = dataclasses.field(default_factory=list)
820+
transform_plugins: list = dataclasses.field(default_factory=list)
821+
822+
def to_dict(self) -> dict:
823+
return json.loads(json.dumps(dataclasses.asdict(self), default=str))
824+
825+
795826
@dataclasses.dataclass
796827
class TransformMetadata:
797828
type: str
@@ -802,6 +833,7 @@ class TransformMetadata:
802833
metadata: dict | None = None
803834
sandbox_dir: Path | None = None
804835
id: str | None = None
836+
ctx: TransformContext | None = None
805837

806838

807839
@dataclasses.dataclass

ogc/bblocks/postprocess.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,12 @@ def do_postprocess(bblock: BuildingBlock, light: bool = False) -> bool:
211211
if not light and (not steps or 'transforms' in steps) and bblock.transforms:
212212
logger.info("Running transforms")
213213
apply_transforms(bblock, outputs_path=test_outputs_path, base_url=base_url,
214-
sandbox_dir=sandbox_dir, bblocks_register=bbr)
214+
sandbox_dir=sandbox_dir, bblocks_register=bbr,
215+
github_base_url=github_base_url,
216+
git_repository=additional_metadata.get('gitRepository'),
217+
id_prefix=id_prefix,
218+
imported_register_urls=imported_registers,
219+
transform_plugins=transform_plugins)
215220

216221
if bblock.examples:
217222
for example in bblock.examples:

ogc/bblocks/transform.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,24 @@
2121
from ogc.bblocks.sandbox import ensure_venv, venv_needs_recreate
2222

2323
from ogc.bblocks import mimetypes
24-
from ogc.bblocks.models import BuildingBlock, BuildingBlockRegister, ImportedBBlockProxy, TransformMetadata, TransformResult, BuildingBlockError
24+
from ogc.bblocks.models import BuildingBlock, BuildingBlockRegister, ImportedBBlockProxy, TransformContext, TransformMetadata, TransformResult, BuildingBlockError
2525
from ogc.bblocks.transformers import transformers
2626
from ogc.bblocks.util import sanitize_filename
2727
from ogc.bblocks.validate import validate_transform_output, report_to_dict
2828

2929
_SUBPROCESS_TRANSFORM_TYPES = ('python', 'node')
3030

3131

32+
def _rel(path: Path | str | None, cwd: Path) -> str | None:
33+
if path is None:
34+
return None
35+
p = Path(path)
36+
try:
37+
return str(p.relative_to(cwd))
38+
except ValueError:
39+
return str(p)
40+
41+
3242
def _pip_to_url(pip_spec: str) -> str | None:
3343
"""Derive a human-facing URL from a pip install specifier, or None if not applicable."""
3444
if not pip_spec:
@@ -230,7 +240,12 @@ def apply_transforms(bblock: BuildingBlock,
230240
output_subpath='transforms',
231241
base_url: str | None = None,
232242
sandbox_dir: Path | None = None,
233-
bblocks_register: BuildingBlockRegister | None = None):
243+
bblocks_register: BuildingBlockRegister | None = None,
244+
github_base_url: str | None = None,
245+
git_repository: str | None = None,
246+
id_prefix: str = '',
247+
imported_register_urls: list[str] | None = None,
248+
transform_plugins: list[dict] | None = None):
234249

235250
if not bblock.transforms:
236251
return
@@ -330,6 +345,38 @@ def apply_transforms(bblock: BuildingBlock,
330345
if example_prefixes:
331346
metadata['_prefixes'] = example_prefixes
332347

348+
ctx = TransformContext(
349+
bblock_id=bblock.identifier,
350+
bblock_name=bblock.name,
351+
bblock_version=bblock.version,
352+
bblock_tags=list(bblock.metadata.get('tags') or []),
353+
bblock_files_path=_rel(bblock.files_path, cwd),
354+
bblock_annotated_path=_rel(bblock.annotated_path, cwd),
355+
bblock_metadata=bblock.metadata,
356+
example_index=example_id,
357+
example={k: v for k, v in example.items() if k != 'snippets'},
358+
snippet_index=snippet_id,
359+
snippet={k: v for k, v in snippet.items() if k != 'code'},
360+
output_file=_rel(output_fn, cwd),
361+
output_dir=_rel(output_dir, cwd),
362+
working_dir=str(cwd),
363+
source_schema_path=(
364+
_rel(bblock.schema.path, cwd) if bblock.schema.is_path else bblock.schema.url
365+
) if bblock.schema else None,
366+
annotated_schema_path=_rel(bblock.annotated_schema, cwd) if bblock.annotated_schema.is_file() else None,
367+
jsonld_context_path=_rel(bblock.jsonld_context, cwd) if bblock.jsonld_context.is_file() else None,
368+
shacl_shapes_paths=[
369+
s if isinstance(s, str) else _rel(s, cwd)
370+
for s in (bblock.shacl_shapes or [])
371+
],
372+
base_url=base_url,
373+
github_base_url=github_base_url,
374+
git_repository=git_repository,
375+
id_prefix=id_prefix,
376+
imported_register_urls=list(imported_register_urls or []),
377+
transform_plugins=list(transform_plugins or []),
378+
)
379+
333380
transform_metadata = TransformMetadata(type=transform['type'],
334381
id=transform['id'],
335382
source_mime_type=snippet_mime_type,
@@ -338,7 +385,8 @@ def apply_transforms(bblock: BuildingBlock,
338385
metadata=metadata,
339386
input_data=snippet['code'],
340387
sandbox_dir=transform_sandboxes.get(
341-
transform['id'], sandbox_dir))
388+
transform['id'], sandbox_dir),
389+
ctx=ctx)
342390

343391
try:
344392
result = transformer.transform(transform_metadata)

ogc/bblocks/transformers/_plugin_harness.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@
1818
{"success": false, "output": null, "binary": false, "stderr": "<str>"}
1919
2020
The metadata object passed to transform() has these attributes:
21-
type (str) transform type identifier
22-
transform_content (str) code/script declared in transforms.yaml
23-
input_data (str) example snippet text
21+
type (str) transform type identifier
22+
transform_content (str) code/script declared in transforms.yaml
23+
input_data (str) example snippet text
2424
source_mime_type (str)
2525
target_mime_type (str)
26-
metadata (dict) extra metadata (keys starting with _ excluded)
27-
sandbox_dir None always None in subprocess context
26+
metadata (dict) extra metadata (keys starting with _ excluded)
27+
sandbox_dir None always None in subprocess context
28+
ctx SimpleNamespace | None transform context (bblock, example, register info)
2829
"""
2930
import importlib
3031
import inspect
3132
import json
3233
import sys
34+
import types
3335
from base64 import b64encode
3436

3537

@@ -51,7 +53,7 @@ def _transformer_classes(module):
5153
class _Meta:
5254
"""Minimal TransformMetadata-compatible namespace passed to plugin transform()."""
5355
__slots__ = ('type', 'transform_content', 'input_data',
54-
'source_mime_type', 'target_mime_type', 'metadata', 'sandbox_dir')
56+
'source_mime_type', 'target_mime_type', 'metadata', 'sandbox_dir', 'ctx')
5557

5658

5759
# ---------------------------------------------------------------------------
@@ -87,6 +89,8 @@ def _transform(meta_json: str) -> None:
8789
m.metadata = meta_dict.get('metadata', {})
8890
m.input_data = sys.stdin.buffer.read().decode('utf-8')
8991
m.sandbox_dir = None
92+
ctx_dict = meta_dict.get('context') or {}
93+
m.ctx = types.SimpleNamespace(**ctx_dict) if ctx_dict else None
9094

9195
module_path = meta_dict['module']
9296
transform_type = meta_dict['type']

ogc/bblocks/transformers/node.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def transform(self, metadata: TransformMetadata) -> TransformResult:
4747
'targetMimeType': metadata.target_mime_type,
4848
'metadata': {k: v for k, v in (metadata.metadata or {}).items()
4949
if not k.startswith('_')},
50+
'context': metadata.ctx.to_dict() if metadata.ctx else None,
5051
}
5152

5253
harness = f"""\

ogc/bblocks/transformers/plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def transform(self, metadata: TransformMetadata) -> TransformResult:
8383
'target_mime_type': metadata.target_mime_type,
8484
'metadata': {k: v for k, v in (metadata.metadata or {}).items()
8585
if not k.startswith('_')},
86+
'context': metadata.ctx.to_dict() if metadata.ctx else None,
8687
}
8788

8889
result = subprocess.run(

ogc/bblocks/transformers/python.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ def transform(self, metadata: TransformMetadata) -> TransformResult:
6262
'target_mime_type': metadata.target_mime_type,
6363
'metadata': {k: v for k, v in (metadata.metadata or {}).items()
6464
if not k.startswith('_')},
65+
'context': metadata.ctx.to_dict() if metadata.ctx else None,
6566
}
6667

6768
harness = f"""\
68-
import sys as _sys
69-
transform_metadata = {json.dumps(transform_metadata_dict)}
69+
import sys as _sys, json as _json, types as _types
70+
_d = _json.loads(_sys.argv[1])
71+
if isinstance(_d.get('context'), dict):
72+
_d['context'] = _types.SimpleNamespace(**_d['context'])
73+
transform_metadata = _types.SimpleNamespace(**_d)
7074
input_data = _sys.stdin.read()
7175
output_data = None
7276
_real_stdout = _sys.stdout
@@ -83,7 +87,7 @@ def transform(self, metadata: TransformMetadata) -> TransformResult:
8387

8488
try:
8589
result = subprocess.run(
86-
[str(python_bin), harness_path],
90+
[str(python_bin), harness_path, json.dumps(transform_metadata_dict)],
8791
input=metadata.input_data.encode('utf-8') if isinstance(metadata.input_data, str) else metadata.input_data,
8892
capture_output=True,
8993
)

0 commit comments

Comments
 (0)