From 73569bfb4b22b099310d7909f8718582418f57e6 Mon Sep 17 00:00:00 2001 From: zrgt Date: Wed, 21 Aug 2024 23:39:45 +0200 Subject: [PATCH 1/3] app: Add support of JSON and XML files - This commit adds support for loading AASX, JSON or XML files. So the user can have a mix of these files in the storage folder and the application will load them all. - Changed types of storage to LOCAL_FILE_BACKEND and LOCAL_FILE_READ_ONLY - Adapted and refactored README.md and added more examples for running --- server/README.md | 37 +++++++++++++++++++++++++++---------- server/app/main.py | 27 ++++++++++++++++++--------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/server/README.md b/server/README.md index 090f7631..853f45ef 100644 --- a/server/README.md +++ b/server/README.md @@ -6,8 +6,8 @@ The server currently implements the following interfaces: - [Asset Administration Shell Repository Service][4] - [Submodel Repository Service][5] -It uses the [HTTP API][1] and the [AASX Adapter][7] of the [BaSyx Python SDK][3], to serve AASX files from a given directory. -The AASX files are only read, chages won't persist. +It uses the [HTTP API][1] and the [AASX][7], [JSON][8], and [XML][9] Adapters of the [BaSyx Python SDK][3], to serve regarding files from a given directory. +The files are only read, chages won't persist. Alternatively, the container can also be told to use the [Local-File Backend][2] instead, which stores AAS and Submodels as individual JSON files and allows for persistent changes (except supplementary files, i.e. files referenced by `File` submodel elements). See [below](#options) on how to configure this. @@ -19,14 +19,31 @@ $ docker buildx build -t basyx-python-sdk-http-server . ``` ## Running -Because the server uses the [Local-File Backend][2], the container needs to be provided with the directory `/storage` to store AAS and Submodel JSON files. + +### Storage +The container needs to be provided with the directory `/storage` to store AAS and Submodel files: AASX, JSON, XML or JSON files of Local-File Backend. + This directory can be mapped via the `-v` option from another image or a local directory. To map the directory `storage` inside the container, `-v ./storage:/storage` can be used. The directory `storage` will be created in the current working directory, if it doesn't already exist. +### Port The HTTP server inside the container listens on port 80 by default. To expose it on the host on port 8080, use the option `-p 8080:80` when running it. +### Options +The container can be configured via environment variables: +- `API_BASE_PATH` determines the base path under which all other API paths are made available. + Default: `/api/v3.0` +- `STORAGE_TYPE` can be one of `LOCAL_FILE_READ_ONLY` or `LOCAL_FILE_BACKEND`: + - When set to `LOCAL_FILE_READ_ONLY` (the default), the server will read and serve AASX, JSON, XML files from the storage directory. + The files are not modified, all changes done via the API are only stored in memory. + - When instead set to `LOCAL_FILE`, the server makes use of the [LocalFileBackend][2], where AAS and Submodels are persistently stored as JSON files. + Supplementary files, i.e. files referenced by `File` submodel elements, are not stored in this case. +- `STORAGE_PATH` sets the directory to read the files from *within the container*. If you bind your files to a directory different from the default `/storage`, you can use this variable to adjust the server accordingly. + +### Running Examples + Putting it all together, the container can be started via the following command: ``` $ docker run -p 8080:80 -v ./storage:/storage basyx-python-sdk-http-server @@ -37,13 +54,11 @@ Since Windows uses backslashes instead of forward slashes in paths, you'll have > docker run -p 8080:80 -v .\storage:/storage basyx-python-sdk-http-server ``` -## Options -The container can be configured via environment variables: -- `API_BASE_PATH` determines the base path under which all other API paths are made available. Default: `/api/v3.0` -- `STORAGE_TYPE` can be one of `AASX` or `LOCAL_FILE`: - - When set to `AASX` (the default), the server will read and serve AASX files from the storage directory. The AASX files are not modified, all changes done via the API are only stored in memory. - - When instead set to `LOCAL_FILE`, the server makes use of the [LocalFileBackend][2], where AAS and Submodels are persistently stored as JSON files. Supplementary files, i.e. files referenced by `File` submodel elements, are not stored in this case. -- `STORAGE_PATH` sets the directory to read the files from *within the container*. If you bind your files to a directory different from the default `/storage`, you can use this variable to adjust the server accordingly. +Per default, the server will use the `LOCAL_FILE_READ_ONLY` storage type and serve the API under `/api/v3.0` and read files from `/storage`. If you want to change this, you can do so like this: +``` +$ docker run -p 8080:80 -v ./storage2:/storage2 -e API_BASE_PATH=/api/v3.1 -e STORAGE_TYPE=LOCAL_FILE_BACKEND -e STORAGE_PATH=/storage2 basyx-python-sdk-http-server +``` + [1]: https://github.com/eclipse-basyx/basyx-python-sdk/pull/238 [2]: https://basyx-python-sdk.readthedocs.io/en/latest/backend/local_file.html @@ -52,3 +67,5 @@ The container can be configured via environment variables: [5]: https://app.swaggerhub.com/apis/Plattform_i40/SubmodelRepositoryServiceSpecification/V3.0.1_SSP-001 [6]: https://industrialdigitaltwin.org/content-hub/aasspecifications/idta_01002-3-0_application_programming_interfaces [7]: https://basyx-python-sdk.readthedocs.io/en/latest/adapter/aasx.html#adapter-aasx +[8]: https://basyx-python-sdk.readthedocs.io/en/latest/adapter/json.html +[9]: https://basyx-python-sdk.readthedocs.io/en/latest/adapter/xml.html diff --git a/server/app/main.py b/server/app/main.py index 9e9488ed..715fa631 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -2,14 +2,14 @@ import pathlib import sys -from basyx.aas import model +from basyx.aas import model, adapter from basyx.aas.adapter import aasx from basyx.aas.backend.local_file import LocalFileObjectStore from basyx.aas.adapter.http import WSGIApp -storage_path = os.getenv("STORAGE_PATH", "/storage") -storage_type = os.getenv("STORAGE_TYPE", "AASX") +storage_path = os.getenv("STORAGE_PATH", "storage") +storage_type = os.getenv("STORAGE_TYPE", "LOCAL_FILE_READ_ONLY") base_path = os.getenv("API_BASE_PATH") wsgi_optparams = {} @@ -17,21 +17,30 @@ if base_path is not None: wsgi_optparams["base_path"] = base_path -if storage_type == "LOCAL_FILE": +if storage_type == "LOCAL_FILE_BACKEND": application = WSGIApp(LocalFileObjectStore(storage_path), aasx.DictSupplementaryFileContainer(), **wsgi_optparams) -elif storage_type == "AASX": +elif storage_type in "LOCAL_FILE_READ_ONLY": object_store: model.DictObjectStore = model.DictObjectStore() file_store: aasx.DictSupplementaryFileContainer = aasx.DictSupplementaryFileContainer() - for file in pathlib.Path(storage_path).glob("*.aasx"): + for file in pathlib.Path(storage_path).iterdir(): if not file.is_file(): continue print(f"Loading {file}") - with aasx.AASXReader(file) as reader: - reader.read_into(object_store=object_store, file_store=file_store) + + if file.suffix == ".json": + with open(file) as f: + adapter.json.read_aas_json_file_into(object_store, f) + elif file.suffix == ".xml": + with open(file) as f: + adapter.xml.read_aas_xml_file_into(object_store, file) + elif file.suffix == ".aasx": + with aasx.AASXReader(file) as reader: + reader.read_into(object_store=object_store, file_store=file_store) application = WSGIApp(object_store, file_store, **wsgi_optparams) else: - print(f"STORAGE_TYPE must be either LOCAL_FILE or AASX! Current value: {storage_type}", file=sys.stderr) + print(f"STORAGE_TYPE must be either LOCAL_FILE or LOCAL_FILE_READ_ONLY! Current value: {storage_type}", + file=sys.stderr) From f8c81ba4824f2d3704a66f8ed773a8c87510d747 Mon Sep 17 00:00:00 2001 From: zrgt Date: Mon, 2 Sep 2024 16:36:13 +0200 Subject: [PATCH 2/3] app: lower the suffixes Lower the suffixes to also match the filenames with uppercase suffixes --- server/app/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/app/main.py b/server/app/main.py index 715fa631..c8d60aff 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -29,13 +29,13 @@ continue print(f"Loading {file}") - if file.suffix == ".json": + if file.suffix.lower() == ".json": with open(file) as f: adapter.json.read_aas_json_file_into(object_store, f) - elif file.suffix == ".xml": + elif file.suffix.lower() == ".xml": with open(file) as f: adapter.xml.read_aas_xml_file_into(object_store, file) - elif file.suffix == ".aasx": + elif file.suffix.lower() == ".aasx": with aasx.AASXReader(file) as reader: reader.read_into(object_store=object_store, file_store=file_store) @@ -44,3 +44,6 @@ else: print(f"STORAGE_TYPE must be either LOCAL_FILE or LOCAL_FILE_READ_ONLY! Current value: {storage_type}", file=sys.stderr) + +# Lower the suffix +# Lower the suffixes to also match the filenames with uppercase suffixes \ No newline at end of file From 6ad831fece14541d448c47836ca6e6b71eb958ff Mon Sep 17 00:00:00 2001 From: zrgt Date: Tue, 3 Sep 2024 18:17:31 +0200 Subject: [PATCH 3/3] Remove comments --- server/app/main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/app/main.py b/server/app/main.py index c8d60aff..6662c868 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -44,6 +44,3 @@ else: print(f"STORAGE_TYPE must be either LOCAL_FILE or LOCAL_FILE_READ_ONLY! Current value: {storage_type}", file=sys.stderr) - -# Lower the suffix -# Lower the suffixes to also match the filenames with uppercase suffixes \ No newline at end of file