Skip to content

Commit 5be904d

Browse files
Oleksandr Muzyka (EPAM)Oleksandr Muzyka (EPAM)
authored andcommitted
Extend SAMM CLI with new functions + test coverage:
* prettyprint: Format Aspect Model files * to_java: Generate Java classes from Aspect Models * to_asyncapi: Generate AsyncAPI specifications * to_jsonld: Generate JSON-LD representations * to_sql: Generate SQL scripts for Aspect data * to_aas: Generate Asset Administration Shell templates * edit_move: Move model elements between files/namespaces * edit_newversion: Create new versions of models * usage: Show where model elements are used * aas_to_aspect: Convert AAS templates to Aspect Models * aas_list: List submodel templates in AAS files * package_import: Import namespace packages * package_export: Export models as namespace packages - Enhance _call_function method to support different command types (aspect, aas, package) - Add support for repeated parameters (lists in kwargs) - Add integration test suite that: * Tests all possible model transformations * Verifies output file validity * Tests language-specific outputs * Tests package export/import functionality * Uses realistic test Aspect Model
1 parent 842ec27 commit 5be904d

File tree

4 files changed

+1031
-110
lines changed

4 files changed

+1031
-110
lines changed

core/esmf-aspect-meta-model-python/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,25 @@ samm_cli.validate(model_path)
154154

155155
List of SAMMCLI functions:
156156
- validate
157+
- prettyprint
158+
- usage
157159
- to_openapi
158160
- to_schema
159161
- to_json
160162
- to_html
161163
- to_png
162164
- to_svg
165+
- to_java
166+
- to_asyncapi
167+
- to_jsonld
168+
- to_sql
169+
- to_aas
170+
- edit_move
171+
- edit_newversion
172+
- aas_to_aspect
173+
- aas_list
174+
- package_import
175+
- package_export
163176

164177
# Scripts
165178

core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_cli_functions.py

Lines changed: 325 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from os.path import exists, join
1616
from pathlib import Path
17+
from typing import Any
1718

1819
from scripts.download_samm_cli import download_samm_cli
1920

@@ -37,6 +38,14 @@ def _get_client_path():
3738

3839
return cli_path
3940

41+
@staticmethod
42+
def _format_argument(key: str, value: Any) -> str:
43+
"""Helper method to format command line arguments."""
44+
if len(key) == 1:
45+
return f"-{key}={value}"
46+
key_formatted = key.replace("_", "-")
47+
return f"--{key_formatted}={value}"
48+
4049
def _validate_client(self):
4150
"""Validate SAMM CLI.
4251
@@ -45,22 +54,20 @@ def _validate_client(self):
4554
if not exists(self._samm):
4655
download_samm_cli()
4756

48-
def _call_function(self, function_name, path_to_model, *args, **kwargs):
57+
def _call_function(self, function_name, path_to_model, *args, command_type="aspect", **kwargs):
4958
"""Run a SAMM CLI function as a subprocess."""
50-
call_args = [self._samm, "aspect", path_to_model] + function_name.split()
59+
call_args = [self._samm, command_type, path_to_model] + function_name.split()
5160

5261
if args:
5362
call_args.extend([f"-{param}" for param in args])
5463

5564
if kwargs:
5665
for key, value in kwargs.items():
57-
if len(key) == 1:
58-
arg = f"-{key}={value}"
66+
if isinstance(value, (list, tuple)):
67+
for item in value:
68+
call_args.append(self._format_argument(key, item))
5969
else:
60-
key = key.replace("_", "-")
61-
arg = f"--{key}={value}"
62-
63-
call_args.append(arg)
70+
call_args.append(self._format_argument(key, value))
6471

6572
if platform.system() == "Darwin": # macOS
6673
call_args = shlex.join(call_args)
@@ -76,6 +83,36 @@ def validate(self, path_to_model, *args, **kwargs):
7683
"""
7784
self._call_function("validate", path_to_model, *args, **kwargs)
7885

86+
def prettyprint(self, path_to_model, *args, **kwargs):
87+
"""Pretty-print Aspect Model.
88+
89+
Formats the Aspect Model file with proper indentation and structure.
90+
91+
param path_to_model: local path to the aspect model file (*.ttl)
92+
possible arguments:
93+
- output, o: the output will be saved to the given file
94+
- overwrite, w: overwrite the input file (use as flag without value)
95+
- custom-resolver: use an external resolver for the resolution of the model elements
96+
"""
97+
self._call_function("prettyprint", path_to_model, *args, **kwargs)
98+
99+
def usage(self, path_to_model, *args, **kwargs):
100+
"""Shows where model elements are used in an Aspect.
101+
102+
param path_to_model: local path to the aspect model file (*.ttl) or an element URN
103+
possible arguments:
104+
- models_root: when model is a URN, at least one models root must be specified
105+
- custom_resolver: use an external resolver for the resolution of the model elements
106+
107+
Examples:
108+
# Show usage for an Aspect Model file
109+
samm_cli.usage("AspectModelFile.ttl")
110+
111+
# Show usage for an element URN with models root
112+
samm_cli.usage("urn:samm:org.eclipse.example:1.0.0#MyElement", models_root="/path/to/models")
113+
"""
114+
self._call_function("usage", path_to_model, *args, **kwargs)
115+
79116
def to_openapi(self, path_to_model, *args, **kwargs):
80117
"""Generate OpenAPI specification for an Aspect Model.
81118
@@ -157,3 +194,283 @@ def to_svg(self, path_to_model, *args, **kwargs):
157194
- custom-resolver: use an external resolver for the resolution of the model elements
158195
"""
159196
self._call_function("to svg", path_to_model, *args, **kwargs)
197+
198+
def to_java(self, path_to_model, *args, **kwargs):
199+
"""Generate Java classes from an Aspect Model.
200+
201+
param path_to_model: local path to the aspect model file (*.ttl)
202+
possible arguments:
203+
- output_directory, d: output directory to write files to (default: current directory)
204+
- package_name, pn: package to use for generated Java classes
205+
- no_jackson, nj: disable Jackson annotation generation in generated Java classes
206+
(use as flag without value)
207+
- no_jackson_jsonformat_shape, njjs: disable JsonFormat.
208+
Shape annotation generation in generated Java classes (use as flag without value)
209+
- json_type_info, jti: if Jackson annotations are enabled, determines the value of JsonTypeInfo.Id
210+
(default: DEDUCTION)
211+
- template_library_file, tlf: the path and name of the Velocity template file containing the macro library
212+
- execute_library_macros, elm: execute the macros provided in the Velocity macro library
213+
(use as flag without value)
214+
- static, s: generate Java domain classes for a Static Meta Model (use as flag without value)
215+
- custom_resolver: use an external resolver for the resolution of the model elements
216+
- name_prefix, namePrefix: name prefix for generated Aspect, Entity Java classes
217+
- name_postfix, namePostfix: name postfix for generated Aspect, Entity Java classes
218+
"""
219+
self._call_function("to java", path_to_model, *args, **kwargs)
220+
221+
def to_asyncapi(self, path_to_model, *args, **kwargs):
222+
"""Generate AsyncAPI specification for an Aspect Model.
223+
224+
param path_to_model: local path to the aspect model file (*.ttl)
225+
possible arguments:
226+
- output, o: output file path (default: stdout)
227+
- channel_address, ca: sets the channel address (i.e., for MQTT, the topic's name)
228+
- application_id, ai: sets the application id, e.g. an identifying URL
229+
- semantic_version, sv: use the full semantic version from the Aspect Model
230+
as the version for the Aspect API (use as flag without value)
231+
- language, l: the language from the model for which an AsyncAPI specification should be generated
232+
(default: en)
233+
- separate_files, sf: create separate files for each schema (use as flag without value)
234+
- custom_resolver: use an external resolver for the resolution of the model elements
235+
"""
236+
self._call_function("to asyncapi", path_to_model, *args, **kwargs)
237+
238+
def to_jsonld(self, path_to_model, *args, **kwargs):
239+
"""Generate JSON-LD representation of an Aspect Model.
240+
241+
param path_to_model: local path to the aspect model file (*.ttl)
242+
possible arguments:
243+
- output, o: output file path (default: stdout)
244+
- custom_resolver: use an external resolver for the resolution of the model elements
245+
"""
246+
self._call_function("to jsonld", path_to_model, *args, **kwargs)
247+
248+
def to_sql(self, path_to_model, *args, **kwargs):
249+
"""Generate SQL script that sets up a table for data for this Aspect.
250+
251+
param path_to_model: local path to the aspect model file (*.ttl)
252+
possible arguments:
253+
- output, o: output file path (default: stdout)
254+
- language, l: the language from the model to use for generated comments
255+
- dialect, d: the SQL dialect to generate for (default: databricks)
256+
- mapping_strategy, s: the mapping strategy to use (default: denormalized)
257+
- include_table_comment, tc: include table comment in the generated SQL script (default: true)
258+
- include_column_comments, cc: include column comments in the generated SQL script (default: true)
259+
- table_command_prefix, tcp: the prefix to use for Databricks table creation commands
260+
(default: CREATE TABLE IF NOT EXISTS)
261+
- decimal_precision, dp: the precision to use for Databricks decimal columns (default: 10)
262+
- custom_column, col: additional custom column definition, e.g. for databricks following the pattern
263+
column_name DATATYPE [NOT NULL] [COMMENT 'custom'].
264+
Note: For multiple custom columns, see the special handling in examples.
265+
- custom_resolver: use an external resolver for the resolution of the model elements
266+
267+
Examples:
268+
# Generate SQL script to stdout with defaults
269+
samm_cli.to_sql("AspectModel.ttl")
270+
271+
# Generate SQL script to file with specific dialect
272+
samm_cli.to_sql("AspectModel.ttl", output="schema.sql", dialect="databricks")
273+
# or using short forms
274+
samm_cli.to_sql("AspectModel.ttl", o="schema.sql", d="databricks")
275+
276+
# Generate with custom settings
277+
samm_cli.to_sql("AspectModel.ttl",
278+
output="schema.sql",
279+
language="de",
280+
mapping_strategy="denormalized",
281+
include_table_comment="false",
282+
decimal_precision="15")
283+
284+
# Generate with custom column
285+
samm_cli.to_sql("AspectModel.ttl", custom_column="column_name STRING NOT NULL COMMENT 'custom'")
286+
287+
# For multiple custom columns, you may need to modify the _call_function method
288+
# or call the CLI directly. Current implementation supports single custom column.
289+
samm_cli.to_sql("AspectModel.ttl", custom_column=("column1 STRING", "column2 INT"))
290+
"""
291+
self._call_function("to sql", path_to_model, *args, **kwargs)
292+
293+
def to_aas(self, path_to_model, *args, **kwargs):
294+
"""Generate an Asset Administration Shell (AAS) submodel template from an Aspect Model.
295+
296+
param path_to_model: local path to the aspect model file (*.ttl)
297+
possible arguments:
298+
- output, o: output file path (default: stdout)
299+
- format, f: output file format (XML, JSON, or AASX, default: XML)
300+
- aspect_data, a: path to a JSON file containing aspect data corresponding to the Aspect Model
301+
- custom_resolver: use an external resolver for the resolution of the model elements
302+
"""
303+
self._call_function("to aas", path_to_model, *args, **kwargs)
304+
305+
def edit_move(self, path_to_model, element, namespace=None, *args, **kwargs):
306+
"""Move a model element definition from its current place to another existing or
307+
new file in the same or another namespace.
308+
309+
param path_to_model: local path to the aspect model file (*.ttl)
310+
param element: the model element to move (e.g., MyAspect otherFile.ttl)
311+
param namespace: optional namespace URN (e.g., urn:samm:org.eclipse.example.myns:1.0.0)
312+
possible arguments:
313+
- dry_run: don't write changes to the file system, but print a report of changes that would be performed
314+
(use as flag)
315+
- details: when used with --dry-run, include details about model content changes in the report
316+
(use as flag)
317+
- copy_file_header: when a model element is moved to a new file,
318+
copy the file header from the source file to the new file (use as flag)
319+
- force: when a new file is to be created, but it already exists in the file system,
320+
the operation will be cancelled, unless --force is used (use as flag)
321+
322+
Examples:
323+
# Move element to another file
324+
samm_cli.edit_move("AspectModel.ttl", "MyAspect otherFile.ttl")
325+
326+
# Move element to another namespace
327+
samm_cli.edit_move("AspectModel.ttl", "MyAspect someFileInOtherNamespace.ttl",
328+
"urn:samm:org.eclipse.example.myns:1.0.0")
329+
330+
# Dry run with details
331+
samm_cli.edit_move("AspectModel.ttl", "MyAspect otherFile.ttl", None, "dry-run", "details")
332+
333+
# Move with copy file header and force
334+
samm_cli.edit_move("AspectModel.ttl", "MyAspect newFile.ttl", None, "copy-file-header", "force")
335+
"""
336+
# Build the function name with positional arguments
337+
function_name = f"edit move {element}"
338+
if namespace:
339+
function_name += f" {namespace}"
340+
341+
self._call_function(function_name, path_to_model, *args, **kwargs)
342+
343+
def edit_newversion(self, path_to_model, version_type=None, *args, **kwargs):
344+
"""Create a new version of an existing file or a complete namespace.
345+
346+
param path_to_model: local path to the aspect model file (*.ttl) or a namespace URN
347+
param version_type: version update type - "major", "minor", or "micro" (optional, pass as flag)
348+
possible arguments:
349+
- major: update the major version (use as flag)
350+
- minor: update the minor version (use as flag)
351+
- micro: update the micro version (use as flag)
352+
- dry_run: don't write changes to the file system,
353+
but print a report of changes that would be performed (use as flag)
354+
- details: when used with --dry-run, include details about model content changes in the report (use as flag)
355+
- force: when a new file is to be created, but it already exists in the file system,
356+
the operation will be cancelled, unless --force is used (use as flag)
357+
- models_root: when model is a URN, at least one models root must be specified
358+
359+
Examples:
360+
# Update major version
361+
samm_cli.edit_newversion("AspectModel.ttl", "major")
362+
363+
# Update minor version
364+
samm_cli.edit_newversion("AspectModel.ttl", "minor")
365+
366+
# Update micro version
367+
samm_cli.edit_newversion("AspectModel.ttl", "micro")
368+
369+
# Dry run with details for major version update
370+
samm_cli.edit_newversion("AspectModel.ttl", "major", "dry-run", "details")
371+
372+
# Force update with specific version type
373+
samm_cli.edit_newversion("AspectModel.ttl", "minor", "force")
374+
375+
# Update namespace with models root
376+
samm_cli.edit_newversion("urn:samm:org.eclipse.example:1.0.0", "major", models_root="/path/to/models")
377+
"""
378+
# Add version type to args if specified
379+
args_list = list(args)
380+
if version_type and version_type in ("major", "minor", "micro"):
381+
args_list.insert(0, version_type)
382+
383+
self._call_function("edit newversion", path_to_model, *args_list, **kwargs)
384+
385+
def aas_to_aspect(self, aas_file, *args, **kwargs):
386+
"""Translate Asset Administration Shell (AAS) Submodel Templates to Aspect Models.
387+
388+
param aas_file: path to the AAS file (*.aasx, *.xml, *.json)
389+
possible arguments:
390+
- output_directory, d: output directory to write files to (default: current directory)
391+
- submodel_template, s: selected submodel template for generating;
392+
run aas_list() to list available templates.
393+
Note: For multiple templates, pass as a list.
394+
395+
Examples:
396+
# Convert all submodel templates to Aspect Models
397+
samm_cli.aas_to_aspect("AssetAdminShell.aasx")
398+
399+
# Convert with specific output directory
400+
samm_cli.aas_to_aspect("AssetAdminShell.aasx",
401+
output_directory="./output")
402+
# or using short form
403+
samm_cli.aas_to_aspect("AssetAdminShell.aasx", d="./output")
404+
405+
# Convert specific submodel templates
406+
samm_cli.aas_to_aspect("AssetAdminShell.aasx",
407+
submodel_template=["1", "2"])
408+
# or using short form
409+
samm_cli.aas_to_aspect("AssetAdminShell.aasx", s=["1", "2"])
410+
"""
411+
self._call_function("to aspect", aas_file, *args, command_type="aas", **kwargs)
412+
413+
def aas_list(self, aas_file, *args, **kwargs):
414+
"""Retrieve a list of submodel templates contained within the provided Asset Administration Shell (AAS) file.
415+
416+
param aas_file: path to the AAS file (*.aasx, *.xml, *.json)
417+
418+
Examples:
419+
# List all submodel templates in an AAS file
420+
samm_cli.aas_list("AssetAdminShell.aasx")
421+
"""
422+
self._call_function("list", aas_file, *args, command_type="aas", **kwargs)
423+
424+
def package_import(self, namespace_package, *args, **kwargs):
425+
"""Imports a Namespace Package (file or URL) into a given models' directory.
426+
427+
param namespace_package: path to the namespace package file (.zip) or URL
428+
possible arguments:
429+
- models_root: target models directory (required)
430+
- dry_run: don't write changes to the file system, but print a report of changes that would be performed
431+
(use as flag)
432+
- details: when used with --dry-run, include details about model content changes in the report (use as flag)
433+
- force: when a new file is to be created, but it already exists in the file system,
434+
the operation will be cancelled, unless --force is used (use as flag)
435+
436+
Examples:
437+
# Import a package into models directory
438+
samm_cli.package_import("MyPackage.zip", models_root="c:\\models")
439+
440+
# Import from URL
441+
samm_cli.package_import("https://example.com/package.zip", models_root="/path/to/models")
442+
443+
# Dry run with details
444+
samm_cli.package_import("MyPackage.zip", "dry-run", "details", models_root="c:\\models")
445+
446+
# Force import
447+
samm_cli.package_import("MyPackage.zip", "force", models_root="c:\\models")
448+
"""
449+
self._call_function("import", namespace_package, *args, command_type="package", **kwargs)
450+
451+
def package_export(self, model_or_urn, *args, **kwargs):
452+
"""Exports an Aspect Model with its dependencies or a complete namespace to a Namespace Package (.zip).
453+
454+
param model_or_urn: path to aspect model file (*.ttl) or namespace URN
455+
possible arguments:
456+
- output, o: output file path (default: stdout); as ZIP is a binary format,
457+
it is strongly recommended to output the result to a file
458+
- models_root: when exporting a namespace URN, models root must be specified
459+
460+
Examples:
461+
# Export an Aspect Model with dependencies
462+
samm_cli.package_export("AspectModel.ttl", output="package.zip")
463+
# or using short form
464+
samm_cli.package_export("AspectModel.ttl", o="package.zip")
465+
466+
# Export a complete namespace
467+
samm_cli.package_export(
468+
"urn:samm:org.eclipse.example.myns:1.0.0",
469+
output="package.zip",
470+
models_root="/path/to/models",
471+
)
472+
473+
# Export to specific location
474+
samm_cli.package_export("AspectModel.ttl", output="c:\\exports\\my-package.zip")
475+
"""
476+
self._call_function("export", model_or_urn, *args, command_type="package", **kwargs)

0 commit comments

Comments
 (0)