Skip to content

Commit 23a6f93

Browse files
Add prefix-based inheritance to codegen
This commit adds th epossibility to specify a parent parser for each prefix in a Schema SALAD specification. The idea is that each extensions could come with a different namespace, and would define its own parser for its classes. With this commit, a user can leverage the `--codegen-parent` option to explicitly map a prefix to a parent parser (e.g., a Python module) with a "prefix=package" syntax. For example: ```bash --codegen-parent "https://w3id.org/cwl/salad=schema_salad.metaschema" ``` Inheritance is not able to automatically reconstruct the whole class hierarchy, meaning that if B inherits from A and C inherits from B, the user must manually specify the mapping also for namespace A. However, we do not expect huge parser hierarchies for schema SALAD specifications.
1 parent 25858e5 commit 23a6f93

3 files changed

Lines changed: 42 additions & 5 deletions

File tree

schema_salad/codegen.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def codegen(
3131
examples: str | None = None,
3232
package: str | None = None,
3333
copyright: str | None = None,
34+
parents_map: dict[str, str] | None = None,
3435
spdx_copyright_text: list[str] | None = None,
3536
spdx_license_identifier: str | None = None,
3637
parser_info: str | None = None,
@@ -87,7 +88,11 @@ def codegen(
8788
return
8889
case "python":
8990
gen = PythonCodeGen(
90-
dest, copyright=copyright, parser_info=info, salad_version=salad_version
91+
dest,
92+
copyright=copyright,
93+
parents_map=parents_map,
94+
parser_info=info,
95+
salad_version=salad_version,
9196
)
9297
case "java":
9398
gen = JavaCodeGen(

schema_salad/main.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929
_logger: Final = logging.getLogger("salad")
3030

3131

32+
def parse_kv(pair: str) -> tuple[str, str]:
33+
"""Parse `key=value` arguments into a tuple of `key` and `value`."""
34+
try:
35+
k, v = pair.split("=", 1)
36+
return k, v
37+
except ValueError as e:
38+
raise argparse.ArgumentTypeError(f"Invalid format: '{pair}', expected key=value") from e
39+
40+
3241
def printrdf(
3342
workflow: str,
3443
wf: CommentedMap | CommentedSeq,
@@ -147,6 +156,15 @@ def arg_parser() -> argparse.ArgumentParser:
147156
"from the base URL (Java/TypeScript/.Net/Dlang only).",
148157
)
149158

159+
codegen_opts.add_argument(
160+
"--codegen-parent",
161+
type=parse_kv,
162+
metavar="PARENTS_MAP",
163+
action="append",
164+
default=None,
165+
help="Optional mapping of prefix into parent package for generated classes (Python only).",
166+
)
167+
150168
codegen_opts.add_argument(
151169
"--codegen-copyright",
152170
type=str,
@@ -351,6 +369,7 @@ def main(argsl: list[str] | None = None) -> int:
351369
examples=args.codegen_examples,
352370
package=args.codegen_package,
353371
copyright=args.codegen_copyright,
372+
parents_map=dict(args.codegen_parent or []),
354373
spdx_license_identifier=args.codegen_spdx_license_identifier,
355374
spdx_copyright_text=args.codegen_spdx_copyright_text,
356375
parser_info=args.codegen_parser_info,

schema_salad/python_codegen.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def __init__(
8888
self,
8989
out: IO[str],
9090
copyright: str | None,
91+
parents_map: dict[str, str] | None,
9192
parser_info: str,
9293
salad_version: str,
9394
) -> None:
@@ -97,8 +98,10 @@ def __init__(
9798
self.serializer = StringIO()
9899
self.idfield = ""
99100
self.copyright: Final = copyright
101+
self.parents_map: Final = parents_map or {}
100102
self.parser_info: Final = parser_info
101103
self.salad_version: Final = salad_version
104+
self.inherited_classes: dict[str, str] = {}
102105

103106
@staticmethod
104107
def safe_name(name: str) -> str:
@@ -126,6 +129,9 @@ def prologue(self) -> None:
126129
# The original schema is {copyright}.
127130
""".format(copyright=self.copyright))
128131

132+
for parent in self.parents_map.values():
133+
self.out.write(f"import {parent}\n")
134+
129135
python_codegen_support: Final = (
130136
files("schema_salad").joinpath("python_codegen_support.py").read_text("UTF-8")
131137
)
@@ -151,11 +157,15 @@ def begin_class(
151157
idfield: str,
152158
optional_fields: set[str],
153159
) -> None:
154-
classname = self.safe_name(classname)
160+
if (classname := self.safe_name(classname)) in self.inherited_classes:
161+
self.current_class_is_abstract = True
162+
return
155163
self.current_class_is_abstract = abstract
156164

157165
if extends:
158-
ext = ", ".join(self.safe_name(e) for e in extends)
166+
ext = ", ".join(
167+
self.inherited_classes.get(self.safe_name(e), self.safe_name(e)) for e in extends
168+
)
159169
else:
160170
ext = "Saveable"
161171

@@ -447,11 +457,14 @@ def type_loader(
447457
)
448458

449459
case {"type": "record" | "https://w3id.org/cwl/salad#record", "name": name, **rest}:
460+
classname = self.safe_name(name)
461+
if (prefix := name.split("#")[0]) in self.parents_map:
462+
self.inherited_classes[classname] = f"{self.parents_map[prefix]}.{classname}"
450463
return self.declare_type(
451464
TypeDef(
452-
self.safe_name(name) + "Loader",
465+
classname + "Loader",
453466
"_RecordLoader({}, {}, {})".format(
454-
self.safe_name(name),
467+
self.inherited_classes.get(classname, classname),
455468
f"'{container}'" if container is not None else None, # noqa: B907
456469
no_link_check,
457470
),

0 commit comments

Comments
 (0)