mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-12-18 12:11:20 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5702adbdbd | ||
|
|
e3a7932ac4 | ||
|
|
21cd039610 | ||
|
|
0b5dd5393a | ||
|
|
0f7a8ec63b | ||
|
|
6fe30fc6ec | ||
|
|
5f64ec131c | ||
|
|
06bf5fb539 | ||
|
|
bdbb79e131 | ||
|
|
78b94d4fc8 | ||
|
|
6d12371e7b | ||
|
|
fb6146e01d |
46
.github/workflows/publish-to-pypi.yaml
vendored
46
.github/workflows/publish-to-pypi.yaml
vendored
@@ -86,26 +86,26 @@ jobs:
|
||||
'${{ github.ref_name }}' dist/**
|
||||
--repo '${{ github.repository }}'
|
||||
|
||||
publish-to-testpypi:
|
||||
name: Publish Python 🐍 distribution 📦 to TestPyPI
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: testpypi
|
||||
url: https://test.pypi.org/p/pydase
|
||||
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: mandatory for trusted publishing
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish distribution 📦 to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
# publish-to-testpypi:
|
||||
# name: Publish Python 🐍 distribution 📦 to TestPyPI
|
||||
# needs:
|
||||
# - build
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# environment:
|
||||
# name: testpypi
|
||||
# url: https://test.pypi.org/p/pydase
|
||||
#
|
||||
# permissions:
|
||||
# id-token: write # IMPORTANT: mandatory for trusted publishing
|
||||
#
|
||||
# steps:
|
||||
# - name: Download all the dists
|
||||
# uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# name: python-package-distributions
|
||||
# path: dist/
|
||||
# - name: Publish distribution 📦 to TestPyPI
|
||||
# uses: pypa/gh-action-pypi-publish@release/v1
|
||||
# with:
|
||||
# repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
0
.python-version
Normal file
0
.python-version
Normal file
13
README.md
13
README.md
@@ -44,13 +44,13 @@
|
||||
Install pydase using [`poetry`](https://python-poetry.org/):
|
||||
|
||||
```bash
|
||||
poetry add git+https://github.com/tiqi-group/pydase.git
|
||||
poetry add pydase
|
||||
```
|
||||
|
||||
or `pip`:
|
||||
|
||||
```bash
|
||||
pip install git+https://github.com/tiqi-group/pydase.git
|
||||
pip install pydase
|
||||
```
|
||||
<!--installation-end-->
|
||||
|
||||
@@ -282,7 +282,7 @@ Below are the components available in the `pydase.components` module, accompanie
|
||||
|
||||
#### Extending with New Components
|
||||
|
||||
Users can also extend the library by creating custom components. This involves defining the behavior on the Python backend and the visual representation on the frontend. For those looking to introduce new components, the [guide on adding components](./docs/dev-guide/Adding_Components.md) provides detailed steps on achieving this.
|
||||
Users can also extend the library by creating custom components. This involves defining the behavior on the Python backend and the visual representation on the frontend. For those looking to introduce new components, the [guide on adding components](https://pydase.readthedocs.io/en/latest/dev-guide/Adding_Components/) provides detailed steps on achieving this.
|
||||
|
||||
## Understanding Service Persistence
|
||||
|
||||
@@ -422,6 +422,7 @@ For more information about what you can do with the units, please consult the do
|
||||
## Changing the Log Level
|
||||
|
||||
You can change the log level of loguru by either
|
||||
|
||||
1. (RECOMMENDED) setting the `ENVIRONMENT` environment variable to "production" or "development"
|
||||
|
||||
```bash
|
||||
@@ -442,12 +443,12 @@ You can change the log level of loguru by either
|
||||
|
||||
## Documentation
|
||||
|
||||
The full documentation provides more detailed information about `pydase`, including advanced usage examples, API references, and tips for troubleshooting common issues. See the [full documentation](URL_TO_YOUR_DOCUMENTATION) for more information.
|
||||
The full documentation provides more detailed information about `pydase`, including advanced usage examples, API references, and tips for troubleshooting common issues. See the [full documentation](https://pydase.readthedocs.io/en/latest/) for more information.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see [contributing.md](./docs/about/contributing.md) for details on how to contribute.
|
||||
We welcome contributions! Please see [contributing.md](https://pydase.readthedocs.io/en/latest/about/contributing/) for details on how to contribute.
|
||||
|
||||
## License
|
||||
|
||||
`pydase` is licensed under the [MIT License](./LICENSE).
|
||||
`pydase` is licensed under the [MIT License](https://github.com/tiqi-group/pydase/blob/main/LICENSE).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pydase"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases."
|
||||
authors = ["Mose Mueller <mosmuell@ethz.ch>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -83,8 +83,9 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
|
||||
def __check_instance_classes(self) -> None:
|
||||
for attr_name, attr_value in get_class_and_instance_attributes(self).items():
|
||||
# every class defined by the user should inherit from DataService
|
||||
if not attr_name.startswith("_DataService__"):
|
||||
# every class defined by the user should inherit from DataService if it is
|
||||
# assigned to a public attribute
|
||||
if not attr_name.startswith("_"):
|
||||
warn_if_instance_class_does_not_inherit_from_DataService(attr_value)
|
||||
|
||||
def __set_attribute_based_on_type( # noqa:CFQ002
|
||||
@@ -198,22 +199,10 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
value, readonly status, and documentation if any in the resulting dictionary.
|
||||
Attributes and methods starting with an underscore are ignored.
|
||||
|
||||
For attributes, methods, and properties unique to the class (not inherited from
|
||||
the base class), the method uses the format "<prefix>.<key>" for keys in the
|
||||
dictionary. If no prefix is provided, the key format is simply "<key>".
|
||||
|
||||
For nested DataService instances, the method serializes recursively and appends
|
||||
the key of the nested instance to the prefix in the format "<prefix>.<key>".
|
||||
|
||||
For nested DataService instances, the method serializes recursively.
|
||||
For attributes of type list, each item in the list is serialized individually.
|
||||
If an item in the list is an instance of DataService, it is serialized
|
||||
recursively with its key in the format "<prefix>.<key>.<item_id>", where
|
||||
"item_id" is the id of the item itself.
|
||||
|
||||
Args:
|
||||
prefix (str, optional): The prefix for each key in the serialized
|
||||
dictionary. This is mainly used when this method is called recursively to
|
||||
maintain the structure of nested instances.
|
||||
recursively.
|
||||
|
||||
Returns:
|
||||
dict: The serialized instance.
|
||||
@@ -321,16 +310,6 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
"readonly": True,
|
||||
"value": running_task_info,
|
||||
}
|
||||
elif isinstance(getattr(self.__class__, key, None), property):
|
||||
prop: property = getattr(self.__class__, key)
|
||||
result[key] = {
|
||||
"type": type(value).__name__,
|
||||
"value": value
|
||||
if not isinstance(value, u.Quantity)
|
||||
else {"magnitude": value.m, "unit": str(value.u)},
|
||||
"readonly": prop.fset is None,
|
||||
"doc": get_attribute_doc(prop),
|
||||
}
|
||||
elif isinstance(value, Enum):
|
||||
result[key] = {
|
||||
"type": "Enum",
|
||||
@@ -352,6 +331,11 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
"doc": get_attribute_doc(value),
|
||||
}
|
||||
|
||||
if isinstance(getattr(self.__class__, key, None), property):
|
||||
prop: property = getattr(self.__class__, key)
|
||||
result[key]["readonly"] = prop.fset is None
|
||||
result[key]["doc"] = get_attribute_doc(prop)
|
||||
|
||||
return result
|
||||
|
||||
def update_DataService_attribute(
|
||||
|
||||
@@ -28,9 +28,8 @@ class AdditionalServerProtocol(Protocol):
|
||||
|
||||
This protocol sets the standard for how additional servers should be implemented
|
||||
to ensure compatibility with the main Server class. The protocol requires that
|
||||
any server implementing it should have an __init__ method for initialization, a
|
||||
serve method for starting the server, and an install_signal_handlers method for
|
||||
setting up signal handlers.
|
||||
any server implementing it should have an __init__ method for initialization and a
|
||||
serve method for starting the server.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
@@ -62,12 +61,6 @@ class AdditionalServerProtocol(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def install_signal_handlers(self) -> None:
|
||||
"""Sets up signal handlers for the server. This method is used to define how the
|
||||
server should respond to various system signals, such as SIGINT and SIGTERM.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class AdditionalServer(TypedDict):
|
||||
"""
|
||||
@@ -257,13 +250,6 @@ class Server:
|
||||
info=self._info,
|
||||
**server["kwargs"],
|
||||
)
|
||||
try:
|
||||
addin_server.install_signal_handlers = lambda: None # type: ignore
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Additional server does not have a method called "
|
||||
"'install_signal_handlers'."
|
||||
)
|
||||
|
||||
server_name = (
|
||||
addin_server.__module__ + "." + addin_server.__class__.__name__
|
||||
|
||||
64
tests/data_service/test_data_service.py
Normal file
64
tests/data_service/test_data_service.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from enum import Enum
|
||||
|
||||
import pydase
|
||||
|
||||
|
||||
def test_enum_serialize() -> None:
|
||||
class EnumClass(Enum):
|
||||
FOO = "foo"
|
||||
BAR = "bar"
|
||||
|
||||
class EnumAttribute(pydase.DataService):
|
||||
def __init__(self) -> None:
|
||||
self.some_enum = EnumClass.FOO
|
||||
super().__init__()
|
||||
|
||||
class EnumPropertyWithoutSetter(pydase.DataService):
|
||||
def __init__(self) -> None:
|
||||
self._some_enum = EnumClass.FOO
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def some_enum(self) -> EnumClass:
|
||||
return self._some_enum
|
||||
|
||||
class EnumPropertyWithSetter(pydase.DataService):
|
||||
def __init__(self) -> None:
|
||||
self._some_enum = EnumClass.FOO
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def some_enum(self) -> EnumClass:
|
||||
return self._some_enum
|
||||
|
||||
@some_enum.setter
|
||||
def some_enum(self, value: EnumClass) -> None:
|
||||
self._some_enum = value
|
||||
|
||||
assert EnumAttribute().serialize() == {
|
||||
"some_enum": {
|
||||
"type": "Enum",
|
||||
"value": "FOO",
|
||||
"enum": {"FOO": "foo", "BAR": "bar"},
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
}
|
||||
}
|
||||
assert EnumPropertyWithoutSetter().serialize() == {
|
||||
"some_enum": {
|
||||
"type": "Enum",
|
||||
"value": "FOO",
|
||||
"enum": {"FOO": "foo", "BAR": "bar"},
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
}
|
||||
}
|
||||
assert EnumPropertyWithSetter().serialize() == {
|
||||
"some_enum": {
|
||||
"type": "Enum",
|
||||
"value": "FOO",
|
||||
"enum": {"FOO": "foo", "BAR": "bar"},
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ from pytest import LogCaptureFixture
|
||||
|
||||
from pydase import DataService
|
||||
|
||||
from . import caplog # noqa
|
||||
from .. import caplog # noqa
|
||||
|
||||
|
||||
def test_setattr_warnings(caplog: LogCaptureFixture) -> None: # noqa
|
||||
@@ -32,3 +32,19 @@ def test_private_attribute_warning(caplog: LogCaptureFixture) -> None: # noqa
|
||||
" Warning: You should not set private but rather protected attributes! Use "
|
||||
"_something instead of __something." in caplog.text
|
||||
)
|
||||
|
||||
|
||||
def test_protected_attribute_warning(caplog: LogCaptureFixture) -> None: # noqa
|
||||
class SubClass:
|
||||
name = "Hello"
|
||||
|
||||
class ServiceClass(DataService):
|
||||
def __init__(self) -> None:
|
||||
self._subclass = SubClass
|
||||
super().__init__()
|
||||
|
||||
ServiceClass()
|
||||
|
||||
assert (
|
||||
"Warning: Class SubClass does not inherit from DataService." not in caplog.text
|
||||
)
|
||||
Reference in New Issue
Block a user