From d4c1a6a0f7c6e02e583023b971d3101f470b8e0a Mon Sep 17 00:00:00 2001 From: Mark <399551+mwiebe@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:05:53 -0700 Subject: [PATCH] docs: Update readme info on creating model objects Some of the sample code in the README.md no longer worked after the change for RFC 0002. https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/rfcs/0002-model-extensions.md updated OpenJD to make the fields and validation of template components depend on an 'extensions' property at the top level of the template. Implementing this in the model library involved adding a model parsing context that objects look at during construction and validation. This change adds a docstring for the parse_model function and updates the broken examples to explain how the extensions fit in and how to create model objects. Signed-off-by: Mark <399551+mwiebe@users.noreply.github.com> --- README.md | 161 +++++++++++++++++++++++-------------- src/openjd/model/_parse.py | 12 +++ 2 files changed, 113 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index d1b9f86..8a76dba 100644 --- a/README.md +++ b/README.md @@ -40,85 +40,126 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for our contributing guidelines. ## Example Usage -### Reading and Validating a Template File +### Reading and Validating a Job Template + +To validate a job template, you can read the JSON or YAML input +into Python data structures and then pass the result to `decode_job_template`. +By default, this will accept templates of any supported version number with +no extensions enabled. Use `decode_environment_template` for environment templates. + +To accept extensions in templates, provide the list of the names you support. See the +[Open Job Description 2023-09 specification](https://github.com/OpenJobDescription/openjd-specifications/wiki/2023-09-Template-Schemas#1-root-elements) +for the list of extensions available. ```python -from openjd.model import ( - DecodeValidationError, - DocumentType, - JobTemplate, - document_string_to_object, - decode_job_template -) +from openjd.model import DocumentType, decode_job_template, document_string_to_object # String containing the json of the job template -template_string = "..." -try: - template_object = document_string_to_object( - document=template_string, - document_type=DocumentType.JSON - ) - # Use 'decode_environment_template()' instead if decoding an - # Environment Template - job_template = decode_job_template(template=template_object) -except DecodeValidationError as e: - print(str(e)) +template_string = """specificationVersion: jobtemplate-2023-09 +name: DemoJob +steps: + - name: DemoStep + script: + actions: + onRun: + command: python + args: ["-c", "print('Hello')"] +""" + +# You can use 'json.loads' or 'yaml.safe_load' directly as well +template_object = document_string_to_object( + document=template_string, + document_type=DocumentType.YAML +) + +# Raises a DecodeValidationError if it fails. +job_template = decode_job_template(template=template_object, supported_extensions=["TASK_CHUNKING"]) ``` -### Creating a Template Model +Once you have the Open Job Description model object, you can use the `model_to_object` function +to convert it into an object suitable for converting to JSON or YAML. ```python -from openjd.model.v2023_09 import * - -job_template = JobTemplate( - specificationVersion="jobtemplate-2023-09", - name="DemoJob", - steps=[ - StepTemplate( - name="DemoStep", - script=StepScript( - actions=StepActions( - onRun=Action( - command="python", - args=["-c", "print('Hello world!')"] - ) - ) - ) - ) - ] +import json +from openjd.model import model_to_object + +obj = model_to_object(model=job_template) +print(json.dumps(obj, indent=2)) +``` + +### Creating Template Model Objects + +As an alternative to assembling full job templates as raw data following the specification data model, +you can use the library to construct model objects of components, such as for StepTemplates, +and then assemble the result into a job template. The `parse_model` function provides a way to +do this. + +To call `parse_model`, you will need to provide the list of extensions you want to enable as the +`supported_extensions` argument. Individual model objects can accept inputs differently depending on +what extensions are requested in the job template, and the model parsing context holds that list. +The functions `decode_job_template` and `decode_environment_template` create this +context from top-level template fields, but when using `parse_model` to process interior model types +you must provide it explicitly. + +```python +import json +from openjd.model import parse_model, model_to_object +from openjd.model.v2023_09 import StepTemplate + +extensions_list = ["TASK_CHUNKING"] + +step_template = parse_model( + model=StepTemplate, + obj={ + "name": "DemoStep", + "script": { + "actions": {"onRun": {"command": "python", "args": ["-c", "print('Hello world!')"]}} + }, + }, + supported_extensions=extensions_list, ) + +obj = model_to_object(model=step_template) +print(json.dumps(obj, indent=2)) ``` -### Converting a Template Model to a Dictionary +You can also construct the individual elements of the template from the model object types. +This can be more effort than using `parse_model` depending on how the enabled extensions +affect processing. You will need to create a ModelParsingContext object to hold +the extensions list, and pass it to any model object constructors that need it. ```python import json -from openjd.model import ( - decode_job_template, - model_to_object, +from openjd.model import model_to_object +from openjd.model.v2023_09 import ( + StepTemplate, + StepScript, + StepActions, + Action, + ArgString, + CommandString, + ModelParsingContext, ) -from openjd.model.v2023_09 import * - -job_template = JobTemplate( - specificationVersion="jobtemplate-2023-09", - name="DemoJob", - steps=[ - StepTemplate( - name="DemoStep", - script=StepScript( - actions=StepActions( - onRun=Action( - command="echo", - args=["Hello world"] - ) - ) + +context = ModelParsingContext(supported_extensions=["TASK_CHUNKING"]) + +step_template = StepTemplate( + name="DemoStep", + script=StepScript( + actions=StepActions( + onRun=Action( + command=CommandString("python", context=context), + args=[ + ArgString("-c", context=context), + ArgString("print('Hello world!')", context=context), + ], ) ) - ] + ), ) -obj = model_to_object(model=job_template) -print(json.dumps(obj)) +obj = model_to_object(model=step_template) +print(json.dumps(obj, indent=2)) ``` ### Creating a Job from a Job Template diff --git a/src/openjd/model/_parse.py b/src/openjd/model/_parse.py index 7032e69..407aee3 100644 --- a/src/openjd/model/_parse.py +++ b/src/openjd/model/_parse.py @@ -96,6 +96,18 @@ def _parse_model(*, model: Type[T], obj: Any, context: Any = None) -> T: def parse_model( *, model: Type[T], obj: Any, supported_extensions: Optional[Iterable[str]] = None ) -> T: + """ + Parses an Open Job Description model object from an object following the Open Job Description + specification. + + Arguments: + model: The Open Job Description model type, e.g. JobTemplate or JobParameterDefinition. + obj: The object to parse, e.g. {"specificationVersion": "2023-09", ...} + supported_extensions (optional): If the model type is a base template like JobTemplate or EnvironmentTemplate, + this is the list of extensions to allow in parsing the object. Otherwise, it is + the list of extensions that are accepted for parsing as if they were listed in the + base template's extensions field. + """ try: return _parse_model( model=model,