Skip to content

Commit c5aaf25

Browse files
Oleksandr Muzyka (EPAM)Oleksandr Muzyka (EPAM)
authored andcommitted
Enhance SAMM CLI functions with capture output option and update tests
1 parent 37de4de commit c5aaf25

File tree

3 files changed

+278
-95
lines changed

3 files changed

+278
-95
lines changed

core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_cli/base.py

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,28 @@ def _validate_client(self):
7474
if not exists(self._samm):
7575
download_samm_cli()
7676

77-
def _call_function(self, function_name, path_to_model, *args, command_type=None, **kwargs):
77+
def _call_function(self, function_name, path_to_model, *args, command_type=None, capture=False, **kwargs) -> str:
7878
"""Run a SAMM CLI function as a subprocess.
7979
8080
Args:
8181
function_name: The SAMM CLI function to call
8282
path_to_model: Path to the model file
8383
*args: Positional arguments (flags)
8484
command_type: Command type (must be one of SAMMCLICommandTypes values)
85+
capture: If True, run with capture_output=True, text=True, check=True and return stdout
8586
**kwargs: Keyword arguments
8687
8788
Raises:
8889
ValueError: If command_type is not one of the allowed types
90+
[subprocess.CalledProcessError]: If capture is True and the subprocess call fails
91+
92+
Returns:
93+
The stdout of the subprocess if capture is True, otherwise None
8994
"""
9095
if command_type is None:
9196
command_type = SAMMCLICommandTypes.ASPECT
9297

98+
call_kwargs = dict()
9399
call_args = [self._samm, command_type, path_to_model] + function_name.split()
94100

95101
if args:
@@ -98,18 +104,23 @@ def _call_function(self, function_name, path_to_model, *args, command_type=None,
98104
if kwargs:
99105
call_args.extend(self._process_kwargs(kwargs))
100106

101-
subprocess.run(call_args)
107+
if capture:
108+
call_kwargs.update(capture_output=True, text=True, check=True)
109+
110+
result = subprocess.run(call_args, **call_kwargs)
111+
112+
return result.stdout
102113

103-
def validate(self, path_to_model, *args, **kwargs):
114+
def validate(self, path_to_model, *args, capture=False, **kwargs):
104115
"""Validate Aspect Model.
105116
106117
param path_to_model: local path to the aspect model file (*.ttl)
107118
possible arguments:
108119
custom-resolver: use an external resolver for the resolution of the model elements
109120
"""
110-
self._call_function(SAMMCLICommands.VALIDATE, path_to_model, *args, **kwargs)
121+
return self._call_function(SAMMCLICommands.VALIDATE, path_to_model, *args, capture=capture, **kwargs)
111122

112-
def prettyprint(self, path_to_model, *args, **kwargs):
123+
def prettyprint(self, path_to_model, *args, capture=False, **kwargs):
113124
"""Pretty-print Aspect Model.
114125
115126
Formats the Aspect Model file with proper indentation and structure.
@@ -120,9 +131,9 @@ def prettyprint(self, path_to_model, *args, **kwargs):
120131
- overwrite, w: overwrite the input file (use as flag without value)
121132
- custom-resolver: use an external resolver for the resolution of the model elements
122133
"""
123-
self._call_function(SAMMCLICommands.PRETTYPRINT, path_to_model, *args, **kwargs)
134+
return self._call_function(SAMMCLICommands.PRETTYPRINT, path_to_model, *args, capture=capture, **kwargs)
124135

125-
def usage(self, path_to_model, *args, **kwargs):
136+
def usage(self, path_to_model, *args, capture=False, **kwargs):
126137
"""Shows where model elements are used in an Aspect.
127138
128139
param path_to_model: local path to the aspect model file (*.ttl) or an element URN
@@ -137,9 +148,9 @@ def usage(self, path_to_model, *args, **kwargs):
137148
# Show usage for an element URN with models root
138149
samm_cli.usage("urn:samm:org.eclipse.example:1.0.0#MyElement", models_root="/path/to/models")
139150
"""
140-
self._call_function(SAMMCLICommands.USAGE, path_to_model, *args, **kwargs)
151+
return self._call_function(SAMMCLICommands.USAGE, path_to_model, *args, capture=capture, **kwargs)
141152

142-
def to_openapi(self, path_to_model, *args, **kwargs):
153+
def to_openapi(self, path_to_model, *args, capture=False, **kwargs):
143154
"""Generate OpenAPI specification for an Aspect Model.
144155
145156
param path_to_model: local path to the aspect model file (*.ttl)
@@ -162,9 +173,9 @@ def to_openapi(self, path_to_model, *args, **kwargs):
162173
- language, l: the language from the model for which an OpenAPI specification should be generated (default: en)
163174
custom-resolver: use an external resolver for the resolution of the model elements
164175
"""
165-
self._call_function(SAMMCLICommands.TO_OPENAPI, path_to_model, *args, **kwargs)
176+
return self._call_function(SAMMCLICommands.TO_OPENAPI, path_to_model, *args, capture=capture, **kwargs)
166177

167-
def to_schema(self, path_to_model, *args, **kwargs):
178+
def to_schema(self, path_to_model, *args, capture=False, **kwargs):
168179
"""Generate JSON schema for an Aspect Model.
169180
170181
param path_to_model: local path to the aspect model file (*.ttl)
@@ -173,19 +184,19 @@ def to_schema(self, path_to_model, *args, **kwargs):
173184
- language, -l: the language from the model for which a JSON schema should be generated (default: en)
174185
- custom-resolver: use an external resolver for the resolution of the model elements
175186
"""
176-
self._call_function(SAMMCLICommands.TO_SCHEMA, path_to_model, *args, **kwargs)
187+
return self._call_function(SAMMCLICommands.TO_SCHEMA, path_to_model, *args, capture=capture, **kwargs)
177188

178-
def to_json(self, path_to_model, *args, **kwargs):
189+
def to_json(self, path_to_model, *args, capture=False, **kwargs):
179190
"""Generate example JSON payload data for an Aspect Model.
180191
181192
param path_to_model: local path to the aspect model file (*.ttl)
182193
possible arguments:
183194
- output, -o: output file path (default: stdout)
184195
- custom-resolver: use an external resolver for the resolution of the model elements
185196
"""
186-
self._call_function(SAMMCLICommands.TO_JSON, path_to_model, *args, **kwargs)
197+
return self._call_function(SAMMCLICommands.TO_JSON, path_to_model, *args, capture=capture, **kwargs)
187198

188-
def to_html(self, path_to_model, *args, **kwargs):
199+
def to_html(self, path_to_model, *args, capture=False, **kwargs):
189200
"""Generate HTML documentation for an Aspect Model.
190201
191202
param path_to_model: local path to the aspect model file (*.ttl)
@@ -195,9 +206,9 @@ def to_html(self, path_to_model, *args, **kwargs):
195206
- language, -l: the language from the model for which the HTML should be generated (default: en)
196207
- custom-resolver: use an external resolver for the resolution of the model elements
197208
"""
198-
self._call_function(SAMMCLICommands.TO_HTML, path_to_model, *args, **kwargs)
209+
return self._call_function(SAMMCLICommands.TO_HTML, path_to_model, *args, capture=capture, **kwargs)
199210

200-
def to_png(self, path_to_model, *args, **kwargs):
211+
def to_png(self, path_to_model, *args, capture=False, **kwargs):
201212
"""Generate PNG diagram for Aspect Model.
202213
203214
param path_to_model: local path to the aspect model file (*.ttl)
@@ -208,9 +219,9 @@ def to_png(self, path_to_model, *args, **kwargs):
208219
- language, -l: the language from the model for which the diagram should be generated (default: en)
209220
- custom-resolver: use an external resolver for the resolution of the model elements
210221
"""
211-
self._call_function(SAMMCLICommands.TO_PNG, path_to_model, *args, **kwargs)
222+
return self._call_function(SAMMCLICommands.TO_PNG, path_to_model, *args, capture=capture, **kwargs)
212223

213-
def to_svg(self, path_to_model, *args, **kwargs):
224+
def to_svg(self, path_to_model, *args, capture=False, **kwargs):
214225
"""Generate SVG diagram for Aspect Model.
215226
216227
param path_to_model: local path to the aspect model file (*.ttl)
@@ -219,9 +230,9 @@ def to_svg(self, path_to_model, *args, **kwargs):
219230
- language, -l: the language from the model for which the diagram should be generated (default: en)
220231
- custom-resolver: use an external resolver for the resolution of the model elements
221232
"""
222-
self._call_function(SAMMCLICommands.TO_SVG, path_to_model, *args, **kwargs)
233+
return self._call_function(SAMMCLICommands.TO_SVG, path_to_model, *args, capture=capture, **kwargs)
223234

224-
def to_java(self, path_to_model, *args, **kwargs):
235+
def to_java(self, path_to_model, *args, capture=False, **kwargs):
225236
"""Generate Java classes from an Aspect Model.
226237
227238
param path_to_model: local path to the aspect model file (*.ttl)
@@ -242,9 +253,9 @@ def to_java(self, path_to_model, *args, **kwargs):
242253
- name_prefix, namePrefix: name prefix for generated Aspect, Entity Java classes
243254
- name_postfix, namePostfix: name postfix for generated Aspect, Entity Java classes
244255
"""
245-
self._call_function(SAMMCLICommands.TO_JAVA, path_to_model, *args, **kwargs)
256+
return self._call_function(SAMMCLICommands.TO_JAVA, path_to_model, *args, capture=capture, **kwargs)
246257

247-
def to_asyncapi(self, path_to_model, *args, **kwargs):
258+
def to_asyncapi(self, path_to_model, *args, capture=False, **kwargs):
248259
"""Generate AsyncAPI specification for an Aspect Model.
249260
250261
param path_to_model: local path to the aspect model file (*.ttl)
@@ -259,19 +270,19 @@ def to_asyncapi(self, path_to_model, *args, **kwargs):
259270
- separate_files, sf: create separate files for each schema (use as flag without value)
260271
- custom_resolver: use an external resolver for the resolution of the model elements
261272
"""
262-
self._call_function(SAMMCLICommands.TO_ASYNCAPI, path_to_model, *args, **kwargs)
273+
return self._call_function(SAMMCLICommands.TO_ASYNCAPI, path_to_model, *args, capture=capture, **kwargs)
263274

264-
def to_jsonld(self, path_to_model, *args, **kwargs):
275+
def to_jsonld(self, path_to_model, *args, capture=False, **kwargs):
265276
"""Generate JSON-LD representation of an Aspect Model.
266277
267278
param path_to_model: local path to the aspect model file (*.ttl)
268279
possible arguments:
269280
- output, o: output file path (default: stdout)
270281
- custom_resolver: use an external resolver for the resolution of the model elements
271282
"""
272-
self._call_function(SAMMCLICommands.TO_JSONLD, path_to_model, *args, **kwargs)
283+
return self._call_function(SAMMCLICommands.TO_JSONLD, path_to_model, *args, capture=capture, **kwargs)
273284

274-
def to_sql(self, path_to_model, *args, **kwargs):
285+
def to_sql(self, path_to_model, *args, capture=False, **kwargs):
275286
"""Generate SQL script that sets up a table for data for this Aspect.
276287
277288
param path_to_model: local path to the aspect model file (*.ttl)
@@ -314,9 +325,9 @@ def to_sql(self, path_to_model, *args, **kwargs):
314325
# or call the CLI directly. Current implementation supports single custom column.
315326
samm_cli.to_sql("AspectModel.ttl", custom_column=("column1 STRING", "column2 INT"))
316327
"""
317-
self._call_function(SAMMCLICommands.TO_SQL, path_to_model, *args, **kwargs)
328+
return self._call_function(SAMMCLICommands.TO_SQL, path_to_model, *args, capture=capture, **kwargs)
318329

319-
# def to_aas(self, path_to_model, *args, **kwargs): # FIXME: https://github.com/eclipse-esmf/esmf-sdk/issues/802
330+
# def to_aas(self, path_to_model, *args, capture=False, **kwargs): # FIXME: https://github.com/eclipse-esmf/esmf-sdk/issues/802
320331
# """Generate an Asset Administration Shell (AAS) submodel template from an Aspect Model.
321332
#
322333
# param path_to_model: local path to the aspect model file (*.ttl)
@@ -326,9 +337,9 @@ def to_sql(self, path_to_model, *args, **kwargs):
326337
# - aspect_data, a: path to a JSON file containing aspect data corresponding to the Aspect Model
327338
# - custom_resolver: use an external resolver for the resolution of the model elements
328339
# """
329-
# self._call_function(SAMMCLICommands.AAS_TO_ASPECT, path_to_model, *args, **kwargs)
340+
# return self._call_function(SAMMCLICommands.AAS_TO_ASPECT, path_to_model, *args, capture=capture, **kwargs)
330341

331-
def edit_move(self, path_to_model, element, namespace=None, *args, **kwargs):
342+
def edit_move(self, path_to_model, element, namespace=None, *args, capture=False, **kwargs):
332343
"""Move a model element definition from its current place to another existing or
333344
new file in the same or another namespace.
334345
@@ -364,9 +375,9 @@ def edit_move(self, path_to_model, element, namespace=None, *args, **kwargs):
364375
if namespace:
365376
function_name += f" {namespace}"
366377

367-
self._call_function(function_name, path_to_model, *args, **kwargs)
378+
return self._call_function(function_name, path_to_model, *args, capture=capture, **kwargs)
368379

369-
def edit_newversion(self, path_to_model, version_type=None, *args, **kwargs):
380+
def edit_newversion(self, path_to_model, version_type=None, *args, capture=False, **kwargs):
370381
"""Create a new version of an existing file or a complete namespace.
371382
372383
param path_to_model: local path to the aspect model file (*.ttl) or a namespace URN
@@ -405,10 +416,11 @@ def edit_newversion(self, path_to_model, version_type=None, *args, **kwargs):
405416
args_list = list(args)
406417
if version_type and version_type in ("major", "minor", "micro"):
407418
args_list.insert(0, version_type)
419+
return self._call_function(
420+
SAMMCLICommands.EDIT_NEWVERSION, path_to_model, *args_list, capture=capture, **kwargs
421+
)
408422

409-
self._call_function(SAMMCLICommands.EDIT_NEWVERSION, path_to_model, *args_list, **kwargs)
410-
411-
def aas_to_aspect(self, aas_file, *args, **kwargs):
423+
def aas_to_aspect(self, aas_file, *args, capture=False, **kwargs):
412424
"""Translate Asset Administration Shell (AAS) Submodel Templates to Aspect Models.
413425
414426
param aas_file: path to the AAS file (*.aasx, *.xml, *.json)
@@ -434,11 +446,16 @@ def aas_to_aspect(self, aas_file, *args, **kwargs):
434446
# or using short form
435447
samm_cli.aas_to_aspect("AssetAdminShell.aasx", s=["1", "2"])
436448
"""
437-
self._call_function(
438-
SAMMCLICommands.AAS_TO_ASPECT, aas_file, *args, command_type=SAMMCLICommandTypes.AAS, **kwargs
449+
return self._call_function(
450+
SAMMCLICommands.AAS_TO_ASPECT,
451+
aas_file,
452+
*args,
453+
command_type=SAMMCLICommandTypes.AAS,
454+
capture=capture,
455+
**kwargs,
439456
)
440457

441-
def aas_list(self, aas_file, *args, **kwargs):
458+
def aas_list(self, aas_file, *args, capture=False, **kwargs):
442459
"""Retrieve a list of submodel templates contained within the provided Asset Administration Shell (AAS) file.
443460
444461
param aas_file: path to the AAS file (*.aasx, *.xml, *.json)
@@ -447,9 +464,16 @@ def aas_list(self, aas_file, *args, **kwargs):
447464
# List all submodel templates in an AAS file
448465
samm_cli.aas_list("AssetAdminShell.aasx")
449466
"""
450-
self._call_function(SAMMCLICommands.AAS_LIST, aas_file, *args, command_type=SAMMCLICommandTypes.AAS, **kwargs)
467+
return self._call_function(
468+
SAMMCLICommands.AAS_LIST,
469+
aas_file,
470+
*args,
471+
command_type=SAMMCLICommandTypes.AAS,
472+
capture=capture,
473+
**kwargs,
474+
)
451475

452-
def package_import(self, namespace_package, *args, **kwargs):
476+
def package_import(self, namespace_package, *args, capture=False, **kwargs):
453477
"""Imports a Namespace Package (file or URL) into a given models' directory.
454478
455479
param namespace_package: path to the namespace package file (.zip) or URL
@@ -474,11 +498,16 @@ def package_import(self, namespace_package, *args, **kwargs):
474498
# Force import
475499
samm_cli.package_import("MyPackage.zip", "force", models_root="c:\\models")
476500
"""
477-
self._call_function(
478-
SAMMCLICommands.PACKAGE_IMPORT, namespace_package, *args, command_type=SAMMCLICommandTypes.PACKAGE, **kwargs
501+
return self._call_function(
502+
SAMMCLICommands.PACKAGE_IMPORT,
503+
namespace_package,
504+
*args,
505+
command_type=SAMMCLICommandTypes.PACKAGE,
506+
capture=capture,
507+
**kwargs,
479508
)
480509

481-
def package_export(self, model_or_urn, *args, **kwargs):
510+
def package_export(self, model_or_urn, *args, capture=False, **kwargs):
482511
"""Exports an Aspect Model with its dependencies or a complete namespace to a Namespace Package (.zip).
483512
484513
param model_or_urn: path to aspect model file (*.ttl) or namespace URN
@@ -503,6 +532,11 @@ def package_export(self, model_or_urn, *args, **kwargs):
503532
# Export to specific location
504533
samm_cli.package_export("AspectModel.ttl", output="c:\\exports\\my-package.zip")
505534
"""
506-
self._call_function(
507-
SAMMCLICommands.PACKAGE_EXPORT, model_or_urn, *args, command_type=SAMMCLICommandTypes.PACKAGE, **kwargs
535+
return self._call_function(
536+
SAMMCLICommands.PACKAGE_EXPORT,
537+
model_or_urn,
538+
*args,
539+
command_type=SAMMCLICommandTypes.PACKAGE,
540+
capture=capture,
541+
**kwargs,
508542
)

core/esmf-aspect-meta-model-python/tests/integration/cli_functions.py renamed to core/esmf-aspect-meta-model-python/tests/integration/test_cli_functions.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH
2+
#
3+
# See the AUTHORS file(s) distributed with this work for additional
4+
# information regarding authorship.
5+
#
6+
# This Source Code Form is subject to the terms of the Mozilla Public
7+
# License, v. 2.0. If a copy of the MPL was not distributed with this
8+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
9+
#
10+
# SPDX-License-Identifier: MPL-2.0
111
"""Integration tests for SAMM CLI functions."""
212

313
import json
@@ -41,18 +51,29 @@ def samm_cli():
4151
class TestSammCliIntegration:
4252
"""Integration tests for SAMM CLI transformations."""
4353

44-
def test_prettyprint(self, samm_cli, file_path, temp_output_dir):
45-
"""Test pretty-printing the model."""
46-
output_file = os.path.join(temp_output_dir, "prettyprinted.ttl")
54+
class TestPrettyPrint:
55+
"""Test pretty-printing functionality."""
4756

48-
samm_cli.prettyprint(file_path, output=output_file)
57+
def test_file_output(self, samm_cli, file_path, temp_output_dir):
58+
"""Test pretty-printing to a file."""
59+
output_file = os.path.join(temp_output_dir, "prettyprinted.ttl")
4960

50-
assert os.path.exists(output_file)
51-
assert os.path.getsize(output_file) > 0
52-
with open(output_file, "r") as f:
53-
content = f.read()
54-
assert "@prefix" in content
55-
assert ":SampleAspect" in content
61+
samm_cli.prettyprint(file_path, output=output_file)
62+
63+
assert os.path.exists(output_file)
64+
assert os.path.getsize(output_file) > 0
65+
with open(output_file, "r") as f:
66+
content = f.read()
67+
assert "@prefix" in content
68+
assert ":SampleAspect" in content
69+
70+
def test_capture_output(self, samm_cli, file_path):
71+
"""Test pretty-printing with captured output."""
72+
prettyprinted_content = samm_cli.prettyprint(file_path, capture=True)
73+
74+
assert isinstance(prettyprinted_content, str)
75+
assert "@prefix" in prettyprinted_content
76+
assert ":SampleAspect" in prettyprinted_content
5677

5778
def test_to_json(self, samm_cli, file_path, temp_output_dir):
5879
"""Test generating example JSON payload."""

0 commit comments

Comments
 (0)