Skip to content

Extract server apps and funcs from sdk into server #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 59 commits into from
Jul 15, 2025

Conversation

zrgt
Copy link
Contributor

@zrgt zrgt commented May 22, 2025

This PR separates server functionalities (Repository apps, in the future also Discovery, and Registries)
from the core SDK to improve modularity and maintainability.
It also includes some refactoring to avoid the code duplication in future.

Changes:

  • Moved all server-related classes and functions from sdk to server
  • Splitted http.py into repository.py, base.py and util/converters.py
  • Created parent classes BaseWSGIApp and ObjectStoreWSGIApp for repository app class,
    which will also be used for discovery and registry apps in the future
  • Added an initial pyproject.toml for the server app
  • Refactored object_hook() by extracting the mapping dict into _get_aas_class_parsers()
  • Refactored default() by extracting the mapping dict into _get_aas_class_serializers()
  • Refactored _create_dict()
  • Created a JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES tuple with top-level JSON keys
    and the corresponding SDK types to reuse it in JSON de-/serialization methods

This PR should be squashed.

Ornella33 and others added 30 commits March 25, 2025 13:07
This refactoring separates server functionalities (Discovery, Registries, Repositories) from the core SDK to improve modularity and maintainability.

Changes:
- Server is now a separate app
- Added an initial pyproject.toml for the server app
- Moved all server-related classes and functions from sdk to server
- Consolidated descriptor.py, submodel_descriptor.py, and aas_descriptor.py into a single server_model.py file
Create a static method for the `aas_class_parsers`
so that we can overload this method in
 `ServerAASFromJsonDecoder` and avoid code duplication by copy/paste `object_hook()`
Create a KEYS_TO_TYPE tuple with
top-level JSON keys and the corresponding SDK types.
By providing this tuple as a param for
`read_aas_json_file_into` we can reuse the method in
`read_server_aas_json_file_into`
and avoid code duplication
Create a class method `_get_aas_class_serializers` for the mapping in `default()`
so that we can overload `_get_aas_class_serializers`
in `ServerAASToJsonEncoder` and avoid code duplication by copy/paste `default()`
- Refactor `_create_dict()`
- Add `JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES` in `_generic`
and use it for `_create_dict()`
Create a class method `_get_aas_class_serializers` for the mapping in `default()` and in `read_aas_json_file_into()`
- Use extended `JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES` in
`read_server_aas_json_file_into`
In 'repository' we keep only AAS/Submodel/CD Repository App,
in `http_api_helpers` we keep all classes/funcs which
will be used across discovery/repository/registry apps
All response related from http_api_helpers.py
was moved to `response.py`
We created parent classes `BaseWSGIApp` and `ObjectStoreWSGIApp`
for discovery/registry/repository app classes.
Now we can reuse methods defined in parent classes
and avoid code duplication.
Move all classes from `response.py` to `base.py`.
@s-heppner s-heppner marked this pull request as ready for review July 2, 2025 04:23
Copy link
Contributor

@s-heppner s-heppner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really cool refactor that'll help us a lot in the long run! Thank you a lot!

Other than those small things, this already pretty much LGTM.
When the mypy issues are done, this should be ready to merge.

Comment on lines +37 to +38
from typing import (Dict, Callable, ContextManager, TypeVar, Type,
List, IO, Optional, Set, get_args, Tuple, Iterable, Any)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you import this many, it is nicer and more readable imo, to put each of them in their own line:

from typing import (
    Dict, 
    Callable,
    ...,
    Any,
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but we do not do so in the rest of the project. So do you think we should from now always write each in their own line/ change the other imports in the project? I think the current style is also quite readable already.

Comment on lines 41 to +43
from .._generic import MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, ENTITY_TYPES_INVERSE, \
IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, REFERENCE_TYPES_INVERSE, \
DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO, Path
DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO, Path, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. One import per line with ()

return encoded


class Base64URLConverter(werkzeug.routing.UnicodeConverter):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a better name would be Base64IdentifierConverter?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Base64URLConverter is a better name since we convert from and to base64URL here. What we convert are Identifiers but I think that is less relevant here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about IdentifierToBase64URLConverter?
What's concerning me is that people might try to use this class for other types.
Or, alternatively, how about renaming the "to_python" method to to_identifer?

@moritzsommer
Copy link
Contributor

CI not passing properly is due to the mypy errors fixed in #399. We should rebase or merge these changes into this PR as well. server-static-analysis failed additionally, because the pyproject.toml is in ./sdk, but the working directory here is only ./server.

@s-heppner
Copy link
Contributor

s-heppner commented Jul 4, 2025

server-static-analysis failed additionally...

The SDK seems to install fine beforehand.

To be more precise, it's failing since the pyproject.toml is in /server/app/pyproject.toml, rather than in /server/, however upon closer look, shouldn't the basyx-python-sdk be in the dependencies of the server?

I think what we should do here is:

  • Add basyx-python-sdk (double check package name) into the dependency section of the /server/app/pyproject.toml
  • Adapt the server-static-analysis (and other server CI pipelines) according to this logic:
    - name: Install Python dependencies
      run: |
        python -m pip install --upgrade pip
        pip install ./app[dev]
        pip uninstall basyx-python-sdk
        pip install ../sdk

This way:

  • when someone installs the server via pip install to run locally it simply pulls the newest basyx-python-sdk from PyPI
  • when run in the CI we ensure it is run with exactly the version of the SDK in that commit we want to check

This is somehow uglily related to #386.

@Frosty2500
Copy link
Contributor

I do not think the basyx-python-sdk should be in the dependencies of the server. After all we directly copy it in the Dockerfile, including it in the pyproject.toml would mean also explicitly uninstalling it there. I do not see the use case.

With the current state of the refactor the working directory has to be app if one wants to run the server locally without any import errors. We could document this but since using the Dockerfile fullfills the same use case and is the intended way to use our server I see no need to explain how to run the server locally.
What does everyone else think?

@s-heppner
Copy link
Contributor

I do not think the basyx-python-sdk should be in the dependencies of the server. After all we directly copy it in the Dockerfile, including it in the pyproject.toml would mean also explicitly uninstalling it there. I do not see the use case.

The use case would be running the Service without Docker. I thought that was our suggested way of running as a developer, or is this outdated?

@Frosty2500
Copy link
Contributor

I do not think the basyx-python-sdk should be in the dependencies of the server. After all we directly copy it in the Dockerfile, including it in the pyproject.toml would mean also explicitly uninstalling it there. I do not see the use case.

The use case would be running the Service without Docker. I thought that was our suggested way of running as a developer, or is this outdated?

Since we have decided to keep copying the local sdk into the docker container there is no reason to run the server locally anymore, you get all the same benefits running in docker. @moritzsommer correct me if I am wrong.

zrgt and others added 9 commits July 11, 2025 00:43
…yx#338)

Previously, we used `vars()` to iterate over all attributes of an object
when updating it from another object (`Referable.update_from()`).

However, `vars()` only retrieves the instance's `__dict__`, e.g. the 
object's attributes, but does not include properties. As a result, when 
you use `vars(other)`, you get the `_id_short` attribute instead of the 
`id_short` property.

This made it impossible to utilize custom setter methods we developed
for certain cases, e.g. when properties should be immutable.

This now adapts the `Referable.update_from()` method to utilize `dir()`
instead, which iterates over all attributes and properties of the 
object. We skip callables and private attributes. We use `setattr()` to 
set attributes and properties, so that the correct setter methods will 
be used.

Furthermore, we now raise an error if an immutable property changed 
between the two versions of the object.

Fixes eclipse-basyx#215 

---------

Co-authored-by: Sercan Sahin <s.sahin@iat.rwth-aachen.de>
…basyx#389)

Previously, the build-dependencies for the sphinx autodocumentation were
listed in `sdk/docs/add-requirements.txt`. This is outdated and the new
state of the art is managing all dependencies via `pyproject.toml`.

This refactors the dependencies from `sdk/docs/add-requirements.txt`
into a new optional `[docs]` section in the `pyproject.toml` dependency
definitions.

Furthermore, we adapt the `.github/workflows/ci.yml` to use the new
definitions accordingly.

Fixes eclipse-basyx#384
@Frosty2500 Frosty2500 force-pushed the refactor/server branch 2 times, most recently from d742b3d to 7e7adfa Compare July 10, 2025 23:01
Copy link
Contributor

@s-heppner s-heppner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now. I'll leave #386 open until we extract the part of running the server without Docker into its own issue. Then we can still decide if we want to support that or not.

@s-heppner s-heppner merged commit 7438abf into eclipse-basyx:develop Jul 15, 2025
15 checks passed
@s-heppner s-heppner deleted the refactor/server branch July 15, 2025 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants