Skip to content

Commit 3b5ccfa

Browse files
authored
Merge pull request #15 from rwth-iat/server
This provides a basic and incomplete implementation of a http server. Core features being: - Modular structure with subservices - Many core endpoints implemented with according high coverage unit-tests - Clear structure for better readability and maintainability For further information see - README.md - Implementation status of specific endpoints & features - DESIGN_NOTES.md - Some general thoughts about how and why this project is structured
2 parents 8e05645 + 4c32602 commit 3b5ccfa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1702
-3
lines changed

.github/workflows/ci.yml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ env:
66
X_PYTHON_VERSION: "3.10"
77

88
jobs:
9-
test:
9+
sdk-test:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
@@ -26,7 +26,7 @@ jobs:
2626
pip install coverage
2727
cd ./sdk
2828
pip install .
29-
- name: Test with coverage + unittest
29+
- name: Test sdk with coverage + unittest
3030
run: |
3131
cd ./sdk
3232
coverage run -m unittest
@@ -36,6 +36,39 @@ jobs:
3636
cd ./sdk
3737
coverage report -m
3838
39+
api-test:
40+
runs-on: ubuntu-latest
41+
strategy:
42+
matrix:
43+
python-version: ["3.8", "3.12"]
44+
45+
46+
47+
steps:
48+
- uses: actions/checkout@v4
49+
- name: Set up Python ${{ matrix.python-version }}
50+
uses: actions/setup-python@v2
51+
with:
52+
python-version: ${{ matrix.python-version }}
53+
- name: Install Python dependencies
54+
run: |
55+
python -m pip install --upgrade pip
56+
pip install coverage
57+
cd ./sdk
58+
pip install .
59+
cd ..
60+
cd ./api
61+
pip install .[dev]
62+
- name: Test sdk with coverage + unittest
63+
run: |
64+
cd ./api
65+
coverage run -m unittest
66+
- name: Report test coverage
67+
if: ${{ always() }}
68+
run: |
69+
cd ./api
70+
coverage report -m
71+
3972
static-analysis:
4073
runs-on: ubuntu-latest
4174

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/venv/
88
/sdk/venv
99
sdk/basyx_python_framework.egg-info/
10+
api/basyx_server.egg-info/
1011

1112
# IDE settings
1213
/.idea/

api/DESIGN_NOTES.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Server Design Notes
2+
3+
> [!warning]
4+
> This concept is heavily WIP! Features presented here might not be implemented
5+
6+
## General ideas
7+
The server is implemented using `FastAPI`.
8+
The project is divided into distinct modules (inspired
9+
[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)
10+
) to enhance maintainability and readability.
11+
12+
### API Classes
13+
14+
The `api` module contains classes that define the endpoints exposed by the server. As little logic as possible is implemented here.
15+
16+
### Service Classes
17+
The `services` module contains all the necessary logic to enable the actions requested by the endpoints defined in `api`.
18+
19+
### Shared Data
20+
With this structure all the routers and services are standalone and do not access each other in any way. As endpoints
21+
need to maintain context across service specifications, we use a central `ObjectStore` instance to handle
22+
objects present during runtime.
23+
24+
### server.py
25+
The server.py contains the main class. Here we construct our `ObjectStore` instance and with that the routers.
26+
As every router is standalone and adds onto the current set of endpoints, we can select which specifications we want to add on startup.

api/README.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Server Development Status
2+
3+
> [!warning]
4+
> This README tracks the development progress of the server's endpoints. Endpoints are sorted based on the
5+
> specification's services (Submodel, AASX File Server, ...).
6+
7+
- **Implemented (✅)**: Currently available, but **not necessarily fully functional**.
8+
- **Planned (📅)**: Scheduled for future implementation.
9+
- **Not Planned (❌)**: Currently not scheduled for implementation.
10+
11+
> [!warning]
12+
> The project is WIP and endpoints might be declared as 'Implemented' whilst still having issues.
13+
14+
> Operation Parameters (e.g. level, content, extent) are generally not supported at the moment.
15+
16+
17+
Below is the status table for the endpoints, organized as specified. Content parameters (/$reference, /$metadata, etc.)
18+
will be implemented as separate routes, but are not listed in this table as it's a simple suffix and does only affect
19+
serialization settings.
20+
21+
## AAS Service
22+
| Endpoint | Operation | Description | Status |
23+
|------------------------------------------------------------|-----------|-------------------------------------------------------------------------|--------|
24+
| `/aas/shells` | GET | Returns all Asset Administration Shells ||
25+
| `/aas/shells` | POST | Creates a new Asset Administration Shell ||
26+
| `/aas/shells/{aas_identifier}` | GET | Returns an Asset Administration Shell by ID ||
27+
| `/aas/shells/{aas_identifier}` | PUT | Updates an existing Asset Administration Shell ||
28+
| `/aas/shells/{aas_identifier}` | DELETE | Deletes an Asset Administration Shell ||
29+
| `/aas/shells/{aas_identifier}/asset-information` | GET | Returns the Asset Information of a specific Asset Administration Shell ||
30+
| `/aas/shells/{aas_identifier}/asset-information` | PUT | Replaces the Asset Information of a specific Asset Administration Shell ||
31+
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | GET | Returns the thumbnail file of the Asset Information ||
32+
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | PUT | Replaces the thumbnail file of the Asset Information ||
33+
| `/aas/shells/{aas_identifier}/asset-information/thumbnail` | DELETE | Deletes the thumbnail file of the Asset Information ||
34+
35+
36+
## Submodel Service
37+
| Endpoint | Operation | Description | Status |
38+
|--------------------------------------------------------------------------------------|-----------|--------------------------------------------------------------------------|--------|
39+
| `/submodels` | GET | Retrieve all submodels ||
40+
| `/submodels` | POST | Create a new submodel ||
41+
| `/submodels/{submodel_id}` | GET | Retrieve a submodel by ID ||
42+
| `/submodels/{submodel_id}` | PUT | Replace a submodel by ID ||
43+
| `/submodels/{submodel_id}` | PATCH | Update a submodel by ID | 📅 |
44+
| `/submodels/{submodel_id}` | DELETE | Delete a submodel by ID ||
45+
| `/submodels/{submodel_id}/submodel-elements` | GET | Retrieve all elements of a specific submodel ||
46+
| `/submodels/{submodel_id}/submodel-elements` | POST | Create new elements in a specific submodel | 📅 |
47+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | GET | Retrieve specific elements by short ID in a submodel ||
48+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | POST | Create specific elements by short ID in a submodel ||
49+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | PUT | Update specific elements by short ID in a submodel ||
50+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | DELETE | Delete specific elements by short ID in a submodel ||
51+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}` | PATCH | Partially update specific elements by short ID in a submodel ||
52+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | GET | Retrieve attachments of specific elements by short ID | 📅 |
53+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | PUT | Update attachments of specific elements by short ID | 📅 |
54+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/attachment` | DELETE | Delete attachments of specific elements by short ID | 📅 |
55+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/invoke` | POST | Invoke operations on specific elements by short ID ||
56+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/invoke-async` | POST | Asynchronously invoke operations on specific elements by short ID ||
57+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers` | GET | Retrieve qualifiers for specific elements by short ID | 📅 |
58+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers` | POST | Add qualifiers to specific elements by short ID | 📅 |
59+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | GET | Retrieve qualifiers of a specific type for specific elements by short ID | 📅 |
60+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | PUT | Update qualifiers of a specific type for specific elements by short ID | 📅 |
61+
| `/submodels/{submodel_id}/submodel-elements/{id_shorts}/qualifiers/{qualifier_type}` | DELETE | Delete qualifiers of a specific type for specific elements by short ID | 📅 |
62+
63+
## AASX File Server Interface and Operations
64+
| Endpoint | Operation | Description | Status |
65+
|---------------------------|-----------|----------------------------------------------------|--------|
66+
| `/aasx` | GET | Returns all available AASX packages at the server. ||
67+
| `/aasx/{aasx_package_id}` | GET | Returns a specific AASX package from the server. ||
68+
| `/aasx` | POST | Creates an AASX package at the server. ||
69+
| `/aasx` | PUT | Replaces the AASX package at the server. ||
70+
| `/aasx/{aasx_package_id}` | DELETE | Deletes a specific AASX package at the server. ||
71+
72+
## AAS Registry Service
73+
| Endpoint | Operation | Description | Status |
74+
|--------------------------------------|-----------|----------------------------------------------------|--------|
75+
| `/registry/aas-descriptors` | GET | Returns all Asset Administration Shell Descriptors ||
76+
| `/registry/aas-descriptors/{aas_id}` | GET | Returns an AAS Descriptor by ID ||
77+
| `/registry/aas-descriptors` | POST | Creates an AAS Descriptor ||
78+
| `/registry/aas-descriptors/{aas_id}` | PUT | Updates an AAS Descriptor ||
79+
| `/registry/aas-descriptors/{aas_id}` | DELETE | Deletes an AAS Descriptor ||
80+
81+
## Submodel Registry Service
82+
| Endpoint | Operation | Description | Status |
83+
|------------------------------------------------|-----------|-------------------------------------|--------|
84+
| `/registry/submodel-descriptors` | GET | Returns all Submodel Descriptors ||
85+
| `/registry/submodel-descriptors/{submodel_id}` | GET | Returns a Submodel Descriptor by ID ||
86+
| `/registry/submodel-descriptors` | POST | Creates a Submodel Descriptor ||
87+
| `/registry/submodel-descriptors/{submodel_id}` | PUT | Updates a Submodel Descriptor ||
88+
| `/registry/submodel-descriptors/{submodel_id}` | DELETE | Deletes a Submodel Descriptor ||
89+
90+
## Discovery Service
91+
| Endpoint | Operation | Description | Status |
92+
|-----------------------------------|-----------|--------------------------------------|--------|
93+
| `/discovery/aas-ids` | GET | Returns all AAS IDs by an Asset Link | 📅 |
94+
| `/discovery/asset-links/{aas_id}` | GET | Returns all Asset Links by an AAS ID | 📅 |
95+
| `/discovery/asset-links/{aas_id}` | POST | Posts new Asset Links | 📅 |
96+
| `/discovery/asset-links/{aas_id}` | DELETE | Deletes Asset Links | 📅 |
97+
98+
## AAS Repository Service
99+
| Endpoint | Operation | Description | Status |
100+
|--------------------------------------------|-----------|---------------------------------------------|--------|
101+
| `/shells` | GET | Returns all Asset Administration Shells | 📅 |
102+
| `/shells/{aas_id}` | GET | Returns an Asset Administration Shell by ID | 📅 |
103+
| `/shells/{aas_id}` | PUT | Updates an Asset Administration Shell by ID | 📅 |
104+
| `/shells/{aas_id}` | DELETE | Deletes an Asset Administration Shell by ID | 📅 |
105+
| `/shells/{aas_id}/asset-information` | GET | Returns Asset Information for an AAS | 📅 |
106+
| `/shells/{aas_id}/submodels` | GET | Returns all Submodels for an AAS | 📅 |
107+
| `/shells/{aas_id}/submodels/{submodel_id}` | GET | Returns a Submodel by ID for an AAS | 📅 |
108+
109+
## Submodel Repository Service
110+
| Endpoint | Operation | Description | Status |
111+
|----------------------------|-----------|--------------------------|--------|
112+
| `/submodels` | GET | Returns all Submodels | 📅 |
113+
| `/submodels/{submodel_id}` | GET | Returns a Submodel by ID | 📅 |
114+
| `/submodels/{submodel_id}` | PUT | Updates a Submodel by ID | 📅 |
115+
| `/submodels/{submodel_id}` | DELETE | Deletes a Submodel by ID | 📅 |
116+
117+
## ConceptDescription Repository Service
118+
| Endpoint | Operation | Description | Status |
119+
|--------------------------------------|-----------|-------------------------------------|--------|
120+
| `/concept-descriptions` | GET | Returns all Concept Descriptions | 📅 |
121+
| `/concept-descriptions/{concept_id}` | GET | Returns a Concept Description by ID | 📅 |
122+
| `/concept-descriptions/{concept_id}` | POST | Creates a new Concept Description | 📅 |
123+
| `/concept-descriptions/{concept_id}` | PUT | Updates a Concept Description by ID | 📅 |
124+
| `/concept-descriptions/{concept_id}` | DELETE | Deletes a Concept Description by ID | 📅 |
125+
126+
127+
## SerializationModifiers
128+
### Level
129+
| Value | Status |
130+
|-------|--------|
131+
| Deep ||
132+
| Core ||
133+
### Content
134+
| Value | Status |
135+
|-----------|--------|
136+
| Normal ||
137+
| Reference ||
138+
| Value ||
139+
| Path ||
140+
### Extent
141+
| Value | Status |
142+
|------------------|--------|
143+
| WithoutBLOBValue ||
144+
| WithBLOBValue ||

api/pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[build-system]
2+
requires = ["setuptools >= 61.0"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "basyx-server" # TODO: name is tbd
7+
version = "0.1"
8+
dependencies = ["aas-core3.0","fastapi","basyx-python-framework-base","uvicorn"]
9+
requires-python = ">=3.8, <3.13"
10+
authors = [
11+
{name = "The Eclipse BaSyx Authors"}
12+
]
13+
# TODO: description is tbd
14+
description="The Eclipse BaSyx Server does stuff"
15+
readme = "README.md"
16+
license = {file = "./LICENSE"}
17+
18+
19+
[project.optional-dependencies]
20+
dev = [
21+
"mypy",
22+
"pycodestyle",
23+
"codeblocks",
24+
"coverage",
25+
"httpx"
26+
]

api/server/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .server_main import app

api/server/routes/__init__.py

Whitespace-only changes.

api/server/routes/aas.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Any
2+
3+
from aas_core3.types import Identifiable
4+
from fastapi import APIRouter, Request
5+
6+
from server.services.aas_service import AasService
7+
from server.utils.pagination import Pagination
8+
from basyx import ObjectStore
9+
10+
11+
class AasRouter(Pagination):
12+
def __init__(self, global_obj_store: ObjectStore[Identifiable]):
13+
self.router = APIRouter()
14+
self.service = AasService(global_obj_store)
15+
self._setup_routes()
16+
17+
def _setup_routes(self):
18+
@self.router.get("/shells")
19+
async def get_all_aas() -> Any:
20+
return self.service.get_all_shells_as_jsonable()
21+
22+
@self.router.post("/shells")
23+
async def create_aas(request: Request) -> Any:
24+
body = await request.json()
25+
return self.service.add_shell_from_body(body)
26+
27+
@self.router.get("/shells/$reference")
28+
async def get_all_aas_reference() -> Any:
29+
return {"message": "Content parameters are not supported yet."}
30+
31+
@self.router.get("/shells/{aas_identifier}")
32+
async def get_aas_by_id(aas_identifier: str) -> Any:
33+
return self.service.get_shell_jsonable_by_id(aas_identifier)
34+
35+
@self.router.put("/shells/{aas_identifier}")
36+
async def put_aas(aas_identifier: str, request: Request) -> Any:
37+
# Update shell with given id
38+
body = await request.json()
39+
return self.service.put_shell_by_id(aas_identifier, body)
40+
41+
@self.router.delete("/shells/{aas_identifier}")
42+
async def delete_aas(aas_identifier: str) -> Any:
43+
return self.service.delete_shell_by_id(aas_identifier)
44+
45+
@self.router.get("/shells/{aas_identifier}/asset-information")
46+
async def get_aas_reference_by_id(aas_identifier: str) -> Any:
47+
return self.service.get_asset_information_by_id_as_jsonable(aas_identifier)
48+
49+
@self.router.put("/shells/{aas_identifier}/asset-information")
50+
async def get_aas_reference_by_id(aas_identifier: str, request: Request) -> Any:
51+
body = await request.json()
52+
return self.service.put_asset_information_by_id_from_jsonable(aas_identifier, body)
53+
54+
@self.router.get("/shells/{aas_identifier}/asset-information/thumbnail")
55+
async def get_aas_thumbnail_by_id(aas_identifier: str) -> Any:
56+
return self.service.get_thumbnail_by_id(aas_identifier)
57+
58+
@self.router.put("/shells/{aas_identifier}/asset-information/thumbnail")
59+
async def get_aas_reference_by_id(aas_identifier: str, request: Request) -> Any:
60+
body = await request.json()
61+
return self.service.put_thumbnail_by_id(aas_identifier, body)
62+
63+
@self.router.delete("/shells/{aas_identifier}/asset-information/thumbnail")
64+
async def delete_aas(aas_identifier: str) -> Any:
65+
return self.service.delete_thumbnail_by_id(aas_identifier)
66+
67+
# TODO: Asset-information endpoints
68+
# /shells/{aas_identifier}/$reference GET

0 commit comments

Comments
 (0)