2222_cache_lock = Lock ()
2323
2424
25- def _build_persistent_harness (transform_content : str ) -> str :
25+ def _build_persistent_harness (transform_content : str , transforms_registry : dict ) -> str :
26+ registry_json = json .dumps (transforms_registry )
2627 return f"""\
2728 import sys as _sys, json as _json, types as _types, base64 as _b64, io as _io, traceback as _tb
2829
2930_TRANSFORM_CODE = compile({ repr (transform_content )} , '<transform>', 'exec')
31+ _TRANSFORMS_REGISTRY = _json.loads({ repr (registry_json )} )
3032
3133_real_stdout = _sys.stdout.buffer
3234
35+ # Module-level state for get_transformer
36+ _cycle_set = set()
37+ _callable_cache = {{}}
38+ _compiled_code_cache = {{}}
39+
40+
41+ def get_transformer(bblock_id, transform_id):
42+ key = (bblock_id, transform_id)
43+ if key in _cycle_set:
44+ raise RuntimeError(
45+ f"Cycle detected: transform {{transform_id!r}} of {{bblock_id!r}} is already executing"
46+ )
47+
48+ if key not in _callable_cache:
49+ bblock_entry = _TRANSFORMS_REGISTRY.get(bblock_id)
50+ if bblock_entry is None:
51+ raise KeyError(f"Building block {{bblock_id!r}} not found in transforms registry")
52+ transform_entry = bblock_entry['transforms'].get(transform_id)
53+ if transform_entry is None:
54+ raise KeyError(
55+ f"Transform {{transform_id!r}} not found for building block {{bblock_id!r}}"
56+ )
57+
58+ code_str = transform_entry['code']
59+ if code_str not in _compiled_code_cache:
60+ _compiled_code_cache[code_str] = compile(
61+ code_str, f'<transform:{{bblock_id}}/{{transform_id}}>', 'exec'
62+ )
63+ compiled = _compiled_code_cache[code_str]
64+
65+ deps = (transform_entry.get('metadata') or {{}}).get('dependencies', {{}})
66+ pip_deps = deps.get('pip', [])
67+ if isinstance(pip_deps, str):
68+ pip_deps = [pip_deps]
69+ if pip_deps:
70+ import subprocess as _subproc
71+ _subproc.run(
72+ [_sys.executable, '-m', 'pip', 'install', '--disable-pip-version-check', *pip_deps],
73+ check=True,
74+ )
75+
76+ def _make_callable(_key=key, _bblock_id=bblock_id, _transform_id=transform_id,
77+ _compiled=compiled, _entry=transform_entry, _bb=bblock_entry):
78+ def _callable(content, extra_metadata=None):
79+ _cycle_set.add(_key)
80+ try:
81+ _base_meta = dict(_entry.get('metadata') or {{}})
82+ if extra_metadata:
83+ _base_meta.update(extra_metadata)
84+ _base_meta['_nested_transform'] = True
85+
86+ _ctx = _types.SimpleNamespace(
87+ bblock_id=_bblock_id,
88+ bblock_name=_bb.get('name'),
89+ bblock_version=_bb.get('bblock_metadata', {{}}).get('version'),
90+ bblock_tags=_bb.get('bblock_metadata', {{}}).get('tags', []),
91+ bblock_metadata=_bb.get('bblock_metadata', {{}}),
92+ )
93+ _tm = _types.SimpleNamespace(
94+ source_mime_type=None,
95+ target_mime_type=None,
96+ metadata=_types.SimpleNamespace(**_base_meta),
97+ context=_ctx,
98+ )
99+
100+ if isinstance(content, bytes):
101+ try:
102+ _input = content.decode('utf-8')
103+ except UnicodeDecodeError:
104+ _input = content
105+ else:
106+ _input = content
107+
108+ _ns = {{
109+ 'transform_metadata': _tm,
110+ 'input_data': _input,
111+ 'output_data': None,
112+ 'get_transformer': get_transformer,
113+ }}
114+ exec(_compiled, _ns)
115+ return _ns.get('output_data')
116+ finally:
117+ _cycle_set.discard(_key)
118+ return _callable
119+
120+ _callable_cache[key] = _make_callable()
121+
122+ return _callable_cache[key]
123+
124+
33125for _line in _sys.stdin:
34126 _line = _line.strip()
35127 if not _line:
@@ -50,7 +142,8 @@ def _build_persistent_harness(transform_content: str) -> str:
50142 _sys.stdout = _capture
51143 _sys.stderr = _capture
52144
53- _ns = {{'transform_metadata': transform_metadata, 'input_data': input_data, 'output_data': None}}
145+ _ns = {{'transform_metadata': transform_metadata, 'input_data': input_data, 'output_data': None,
146+ 'get_transformer': get_transformer}}
54147 try:
55148 exec(_TRANSFORM_CODE, _ns)
56149 output_data = _ns.get('output_data')
@@ -78,17 +171,18 @@ def _build_persistent_harness(transform_content: str) -> str:
78171
79172class _PersistentProcess :
80173
81- def __init__ (self , python_bin : Path , transform_content : str ):
174+ def __init__ (self , python_bin : Path , transform_content : str , transforms_registry : dict ):
82175 self ._python_bin = python_bin
83176 self ._transform_content = transform_content
177+ self ._transforms_registry = transforms_registry
84178 self ._proc : subprocess .Popen | None = None
85179 self ._harness_path : Path | None = None
86180 self ._lock = Lock ()
87181 self ._start ()
88182
89183 def _start (self ):
90184 if self ._harness_path is None :
91- harness = _build_persistent_harness (self ._transform_content )
185+ harness = _build_persistent_harness (self ._transform_content , self . _transforms_registry )
92186 with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False ) as f :
93187 f .write (harness )
94188 self ._harness_path = Path (f .name )
@@ -166,11 +260,12 @@ def transform(self, metadata: TransformMetadata) -> TransformResult:
166260 if isinstance (transform_content , bytes ):
167261 transform_content = transform_content .decode ('utf-8' )
168262
263+ transforms_registry = metadata .transforms_registry or {}
169264 cache_key = (str (python_bin ), transform_content )
170265 with _cache_lock :
171266 proc = _process_cache .get (cache_key )
172267 if proc is None :
173- proc = _PersistentProcess (python_bin , transform_content )
268+ proc = _PersistentProcess (python_bin , transform_content , transforms_registry )
174269 _process_cache [cache_key ] = proc
175270
176271 transform_metadata_dict = {
0 commit comments