Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
eb2d2dd
sdk/server: Add basic implementation of some endpoints
JAB1305 Dec 10, 2024
fe23a74
/sdk/basyx/serer/submodel: Add more endpoints
JAB1305 Dec 10, 2024
8f0a58c
sdk/basyx/server: Moved to /server
JAB1305 Dec 14, 2024
c6535e8
api/README.md: Add development status as server README file
JAB1305 Dec 14, 2024
7c56034
api/pyproject.toml: Update pyproject to contain required dependencies
somsonson Dec 16, 2024
c923af7
update .gitignore
somsonson Dec 17, 2024
cad1886
api/server: fix python imports and codestyle
somsonson Dec 17, 2024
cba1723
object_store: Add delete function to delete by id and not only by ins…
somsonson Jan 3, 2025
308ddb6
api/server/aasx: Add AASX File Server Interface
somsonson Jan 3, 2025
6ffa7d8
api/README.md: AASX File Server Interface and Operations specified in…
somsonson Jan 3, 2025
675f788
api/README.md: Update formatting and add all endpoints
JAB1305 Jan 6, 2025
b9a9668
api/server: Add aas registry server and submodel registry server to s…
somsonson Jan 14, 2025
0a39608
uptdate readme.md
somsonson Jan 14, 2025
41aafb8
api/server: Fix imports to support unittests.
somsonson Jan 14, 2025
b48aa8e
api/server: Add submodelElement related endpoints
JAB1305 Jan 20, 2025
7c4adeb
Restructure imports following PEP 8
JAB1305 Feb 4, 2025
55bc7d2
(Hopefully) Resolve imports conflicts by using primarily absolute imp…
somsonson Feb 7, 2025
8120a88
modify testing structure in /api/server/test
somsonson Feb 10, 2025
ea18cc7
fix issue with compatibility to python 3.8
somsonson Feb 10, 2025
b5846db
Add testfiles and include server tests in CI
somsonson Feb 10, 2025
f33e700
add server test to CI
somsonson Feb 10, 2025
25b7ffc
fix testing environment in CI
somsonson Feb 10, 2025
ed2e483
fix typo
somsonson Feb 10, 2025
c38c86d
fix yet another import mistake
somsonson Feb 10, 2025
582f44c
fix multiple backwards compatibility problems
somsonson Feb 10, 2025
813e961
fix backwards compatility for python 3.8
somsonson Feb 10, 2025
b26f1b4
fix more possible compatibility conflicts
somsonson Feb 10, 2025
5ea5797
Stuff
JAB1305 Feb 26, 2025
9d75cf2
Finish AAS, Update tests, Update URLs
JAB1305 Mar 1, 2025
6904345
Merge branch 'server-fixed-imports' into server
JAB1305 Mar 1, 2025
31c124e
Fix imports, fix tests
JAB1305 Mar 1, 2025
8f89954
api/test: Resolve issues with relative json paths
JAB1305 Mar 1, 2025
c50e183
api/test: Adjust aasx test so every case can be executed independantly
JAB1305 Mar 1, 2025
44f57ed
aasx_server: Remove redundant slashes at the end of path to fix unitt…
JAB1305 Mar 1, 2025
bde3c8d
api/test/test_aas_service.py: Fix teardown so tests dont influence ea…
JAB1305 Mar 2, 2025
a822dcb
api: Resolve Union and List style affecting python 3.8 compatibility
JAB1305 Mar 7, 2025
23b3a05
api/server/services/aasservice.py: Delete file as its duplicate and e…
JAB1305 Mar 8, 2025
0f9934d
/api: Add put shell endpoint and unit test
JAB1305 Mar 8, 2025
3eed6ae
api/README.md: Update AASX File server status table
JAB1305 Mar 8, 2025
4c55370
/api: Add asset-information endpoints and tests
JAB1305 Mar 9, 2025
0a51dcc
api/README.md: Update submodel service endpoint addresses
JAB1305 Mar 9, 2025
59a6595
/api: Add more tests based on coverage report
JAB1305 Mar 9, 2025
8729770
api/server/services: Update exception handling to a CustomErrorResponse
JAB1305 Mar 24, 2025
125f89f
api/pyproject.toml: Add empty line at end of file
s-heppner Jun 16, 2025
15e6377
server.routes.aasx_file_server: Add empty line at end of file
s-heppner Jun 16, 2025
4c32602
api.test.examples: Add missing lines at end of files
s-heppner Jun 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ env:
X_PYTHON_VERSION: "3.10"

jobs:
test:
sdk-test:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -26,7 +26,7 @@ jobs:
pip install coverage
cd ./sdk
pip install .
- name: Test with coverage + unittest
- name: Test sdk with coverage + unittest
run: |
cd ./sdk
coverage run -m unittest
Expand All @@ -36,6 +36,39 @@ jobs:
cd ./sdk
coverage report -m

api-test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.12"]



steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install coverage
cd ./sdk
pip install .
cd ..
cd ./api
pip install .[dev]
- name: Test sdk with coverage + unittest
run: |
cd ./api
coverage run -m unittest
- name: Report test coverage
if: ${{ always() }}
run: |
cd ./api
coverage report -m

static-analysis:
runs-on: ubuntu-latest

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/venv/
/sdk/venv
sdk/basyx_python_framework.egg-info/
api/basyx_server.egg-info/

# IDE settings
/.idea/
Expand Down
26 changes: 26 additions & 0 deletions api/DESIGN_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Server Design Notes

> [!warning]
> This concept is heavily WIP! Features presented here might not be implemented

## General ideas
The server is implemented using `FastAPI`.
The project is divided into distinct modules (inspired
[by the server specification](https://industrialdigitaltwin.org/en/wp-content/uploads/sites/2/2024/10/IDTA-01002-3-0-3_SpecificationAssetAdministrationShell_Part2_API.pdf#page=128)
) to enhance maintainability and readability.

### API Classes

The `api` module contains classes that define the endpoints exposed by the server. As little logic as possible is implemented here.

### Service Classes
The `services` module contains all the necessary logic to enable the actions requested by the endpoints defined in `api`.

### Shared Data
With this structure all the routers and services are standalone and do not access each other in any way. As endpoints
need to maintain context across service specifications, we use a central `ObjectStore` instance to handle
objects present during runtime.

### server.py
The server.py contains the main class. Here we construct our `ObjectStore` instance and with that the routers.
As every router is standalone and adds onto the current set of endpoints, we can select which specifications we want to add on startup.
144 changes: 144 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Server Development Status

> [!warning]
> This README tracks the development progress of the server's endpoints. Endpoints are sorted based on the
> specification's services (Submodel, AASX File Server, ...).

- **Implemented (✅)**: Currently available, but **not necessarily fully functional**.
- **Planned (📅)**: Scheduled for future implementation.
- **Not Planned (❌)**: Currently not scheduled for implementation.

> [!warning]
> The project is WIP and endpoints might be declared as 'Implemented' whilst still having issues.

> Operation Parameters (e.g. level, content, extent) are generally not supported at the moment.


Below is the status table for the endpoints, organized as specified. Content parameters (/$reference, /$metadata, etc.)
will be implemented as separate routes, but are not listed in this table as it's a simple suffix and does only affect
serialization settings.

## AAS Service
| Endpoint | Operation | Description | Status |
|------------------------------------------------------------|-----------|-------------------------------------------------------------------------|--------|
| `/aas/shells` | GET | Returns all Asset Administration Shells | ✅ |
| `/aas/shells` | POST | Creates a new Asset Administration Shell | ✅ |
| `/aas/shells/{aas_identifier}` | GET | Returns an Asset Administration Shell by ID | ✅ |
| `/aas/shells/{aas_identifier}` | PUT | Updates an existing Asset Administration Shell | ✅ |
| `/aas/shells/{aas_identifier}` | DELETE | Deletes an Asset Administration Shell | ✅ |
| `/aas/shells/{aas_identifier}/asset-information` | GET | Returns the Asset Information of a specific Asset Administration Shell | ✅ |
| `/aas/shells/{aas_identifier}/asset-information` | PUT | Replaces the Asset Information of a specific Asset Administration Shell | ✅ |
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | GET | Returns the thumbnail file of the Asset Information | ✅ |
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | PUT | Replaces the thumbnail file of the Asset Information | ✅ |
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | DELETE | Deletes the thumbnail file of the Asset Information | ✅ |


## Submodel Service
| Endpoint | Operation | Description | Status |
|--------------------------------------------------------------------------------------|-----------|--------------------------------------------------------------------------|--------|
| `/submodels` | GET | Retrieve all submodels | ✅ |
| `/submodels` | POST | Create a new submodel | ✅ |
| `/submodels/{submodel_id}` | GET | Retrieve a submodel by ID | ✅ |
| `/submodels/{submodel_id}` | PUT | Replace a submodel by ID | ✅ |
| `/submodels/{submodel_id}` | PATCH | Update a submodel by ID | 📅 |
| `/submodels/{submodel_id}` | DELETE | Delete a submodel by ID | ✅ |
| `/submodels/{submodel_id}/submodel-elements` | GET | Retrieve all elements of a specific submodel | ✅ |
| `/submodels/{submodel_id}/submodel-elements` | POST | Create new elements in a specific submodel | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | GET | Retrieve specific elements by short ID in a submodel | ✅ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | POST | Create specific elements by short ID in a submodel | ✅ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | PUT | Update specific elements by short ID in a submodel | ✅ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | DELETE | Delete specific elements by short ID in a submodel | ✅ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | PATCH | Partially update specific elements by short ID in a submodel | ❌ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | GET | Retrieve attachments of specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | PUT | Update attachments of specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | DELETE | Delete attachments of specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/invoke` | POST | Invoke operations on specific elements by short ID | ❌ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/invoke-async` | POST | Asynchronously invoke operations on specific elements by short ID | ❌ |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers` | GET | Retrieve qualifiers for specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers` | POST | Add qualifiers to specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | GET | Retrieve qualifiers of a specific type for specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | PUT | Update qualifiers of a specific type for specific elements by short ID | 📅 |
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | DELETE | Delete qualifiers of a specific type for specific elements by short ID | 📅 |

## AASX File Server Interface and Operations
| Endpoint | Operation | Description | Status |
|---------------------------|-----------|----------------------------------------------------|--------|
| `/aasx` | GET | Returns all available AASX packages at the server. | ✅ |
| `/aasx/{aasx_package_id}` | GET | Returns a specific AASX package from the server. | ✅ |
| `/aasx` | POST | Creates an AASX package at the server. | ✅ |
| `/aasx` | PUT | Replaces the AASX package at the server. | ✅ |
| `/aasx/{aasx_package_id}` | DELETE | Deletes a specific AASX package at the server. | ✅ |

## AAS Registry Service
| Endpoint | Operation | Description | Status |
|--------------------------------------|-----------|----------------------------------------------------|--------|
| `/registry/aas-descriptors` | GET | Returns all Asset Administration Shell Descriptors | ✅ |
| `/registry/aas-descriptors/{aas_id}` | GET | Returns an AAS Descriptor by ID | ✅ |
| `/registry/aas-descriptors` | POST | Creates an AAS Descriptor | ✅ |
| `/registry/aas-descriptors/{aas_id}` | PUT | Updates an AAS Descriptor | ✅ |
| `/registry/aas-descriptors/{aas_id}` | DELETE | Deletes an AAS Descriptor | ✅ |

## Submodel Registry Service
| Endpoint | Operation | Description | Status |
|------------------------------------------------|-----------|-------------------------------------|--------|
| `/registry/submodel-descriptors` | GET | Returns all Submodel Descriptors | ✅ |
| `/registry/submodel-descriptors/{submodel_id}` | GET | Returns a Submodel Descriptor by ID | ✅ |
| `/registry/submodel-descriptors` | POST | Creates a Submodel Descriptor | ✅ |
| `/registry/submodel-descriptors/{submodel_id}` | PUT | Updates a Submodel Descriptor | ✅ |
| `/registry/submodel-descriptors/{submodel_id}` | DELETE | Deletes a Submodel Descriptor | ✅ |

## Discovery Service
| Endpoint | Operation | Description | Status |
|-----------------------------------|-----------|--------------------------------------|--------|
| `/discovery/aas-ids` | GET | Returns all AAS IDs by an Asset Link | 📅 |
| `/discovery/asset-links/{aas_id}` | GET | Returns all Asset Links by an AAS ID | 📅 |
| `/discovery/asset-links/{aas_id}` | POST | Posts new Asset Links | 📅 |
| `/discovery/asset-links/{aas_id}` | DELETE | Deletes Asset Links | 📅 |

## AAS Repository Service
| Endpoint | Operation | Description | Status |
|--------------------------------------------|-----------|---------------------------------------------|--------|
| `/shells` | GET | Returns all Asset Administration Shells | 📅 |
| `/shells/{aas_id}` | GET | Returns an Asset Administration Shell by ID | 📅 |
| `/shells/{aas_id}` | PUT | Updates an Asset Administration Shell by ID | 📅 |
| `/shells/{aas_id}` | DELETE | Deletes an Asset Administration Shell by ID | 📅 |
| `/shells/{aas_id}/asset-information` | GET | Returns Asset Information for an AAS | 📅 |
| `/shells/{aas_id}/submodels` | GET | Returns all Submodels for an AAS | 📅 |
| `/shells/{aas_id}/submodels/{submodel_id}` | GET | Returns a Submodel by ID for an AAS | 📅 |

## Submodel Repository Service
| Endpoint | Operation | Description | Status |
|----------------------------|-----------|--------------------------|--------|
| `/submodels` | GET | Returns all Submodels | 📅 |
| `/submodels/{submodel_id}` | GET | Returns a Submodel by ID | 📅 |
| `/submodels/{submodel_id}` | PUT | Updates a Submodel by ID | 📅 |
| `/submodels/{submodel_id}` | DELETE | Deletes a Submodel by ID | 📅 |

## ConceptDescription Repository Service
| Endpoint | Operation | Description | Status |
|--------------------------------------|-----------|-------------------------------------|--------|
| `/concept-descriptions` | GET | Returns all Concept Descriptions | 📅 |
| `/concept-descriptions/{concept_id}` | GET | Returns a Concept Description by ID | 📅 |
| `/concept-descriptions/{concept_id}` | POST | Creates a new Concept Description | 📅 |
| `/concept-descriptions/{concept_id}` | PUT | Updates a Concept Description by ID | 📅 |
| `/concept-descriptions/{concept_id}` | DELETE | Deletes a Concept Description by ID | 📅 |


## SerializationModifiers
### Level
| Value | Status |
|-------|--------|
| Deep | ❌ |
| Core | ❌ |
### Content
| Value | Status |
|-----------|--------|
| Normal | ❌ |
| Reference | ❌ |
| Value | ❌ |
| Path | ❌ |
### Extent
| Value | Status |
|------------------|--------|
| WithoutBLOBValue | ❌ |
| WithBLOBValue | ❌ |
26 changes: 26 additions & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "basyx-server" # TODO: name is tbd
version = "0.1"
dependencies = ["aas-core3.0","fastapi","basyx-python-framework-base","uvicorn"]
requires-python = ">=3.8, <3.13"
authors = [
{name = "The Eclipse BaSyx Authors"}
]
# TODO: description is tbd
description="The Eclipse BaSyx Server does stuff"
readme = "README.md"
license = {file = "./LICENSE"}


[project.optional-dependencies]
dev = [
"mypy",
"pycodestyle",
"codeblocks",
"coverage",
"httpx"
]
1 change: 1 addition & 0 deletions api/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .server_main import app
Empty file added api/server/routes/__init__.py
Empty file.
68 changes: 68 additions & 0 deletions api/server/routes/aas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import Any

from aas_core3.types import Identifiable
from fastapi import APIRouter, Request

from server.services.aas_service import AasService
from server.utils.pagination import Pagination
from basyx import ObjectStore


class AasRouter(Pagination):
def __init__(self, global_obj_store: ObjectStore[Identifiable]):
self.router = APIRouter()
self.service = AasService(global_obj_store)
self._setup_routes()

def _setup_routes(self):
@self.router.get("/shells")
async def get_all_aas() -> Any:
return self.service.get_all_shells_as_jsonable()

@self.router.post("/shells")
async def create_aas(request: Request) -> Any:
body = await request.json()
return self.service.add_shell_from_body(body)

@self.router.get("/shells/$reference")
async def get_all_aas_reference() -> Any:
return {"message": "Content parameters are not supported yet."}

@self.router.get("/shells/{aas_identifier}")
async def get_aas_by_id(aas_identifier: str) -> Any:
return self.service.get_shell_jsonable_by_id(aas_identifier)

@self.router.put("/shells/{aas_identifier}")
async def put_aas(aas_identifier: str, request: Request) -> Any:
# Update shell with given id
body = await request.json()
return self.service.put_shell_by_id(aas_identifier, body)

@self.router.delete("/shells/{aas_identifier}")
async def delete_aas(aas_identifier: str) -> Any:
return self.service.delete_shell_by_id(aas_identifier)

@self.router.get("/shells/{aas_identifier}/asset-information")
async def get_aas_reference_by_id(aas_identifier: str) -> Any:
return self.service.get_asset_information_by_id_as_jsonable(aas_identifier)

@self.router.put("/shells/{aas_identifier}/asset-information")
async def get_aas_reference_by_id(aas_identifier: str, request: Request) -> Any:
body = await request.json()
return self.service.put_asset_information_by_id_from_jsonable(aas_identifier, body)

@self.router.get("/shells/{aas_identifier}/asset-information/thumbnail")
async def get_aas_thumbnail_by_id(aas_identifier: str) -> Any:
return self.service.get_thumbnail_by_id(aas_identifier)

@self.router.put("/shells/{aas_identifier}/asset-information/thumbnail")
async def get_aas_reference_by_id(aas_identifier: str, request: Request) -> Any:
body = await request.json()
return self.service.put_thumbnail_by_id(aas_identifier, body)

@self.router.delete("/shells/{aas_identifier}/asset-information/thumbnail")
async def delete_aas(aas_identifier: str) -> Any:
return self.service.delete_thumbnail_by_id(aas_identifier)

# TODO: Asset-information endpoints
# /shells/{aas_identifier}/$reference GET
Loading