12 Commits

Author SHA1 Message Date
Mose Müller
5702adbdbd update version 2023-10-10 14:54:17 +02:00
Mose Müller
e3a7932ac4 Merge pull request #26 from tiqi-group/24-issue-pydase-checks-if-protected-variables-inherit-from-dataservice
fix: only check inheritance of public attributes
2023-10-10 12:54:45 +02:00
Mose Müller
21cd039610 fix: only check inheritance of public attributes 2023-10-10 12:51:50 +02:00
Mose Müller
0b5dd5393a remove publish-to-testpypi workflow 2023-10-10 12:35:52 +02:00
Mose Müller
0f7a8ec63b Merge pull request #25 from tiqi-group/20-property-that-returns-an-enum-is-not-correctly-rendered
Fixes serialization for enum properties
2023-10-10 12:32:53 +02:00
Mose Müller
6fe30fc6ec update version 2023-10-10 12:31:43 +02:00
Mose Müller
5f64ec131c feat: adding enum_serialize test 2023-10-10 12:29:45 +02:00
Mose Müller
06bf5fb539 fix: serializing enum properties 2023-10-10 12:18:13 +02:00
Mose Müller
bdbb79e131 feat: removing "install_signal_handlers" method from protocol (would have been overwritten anyway) 2023-10-03 16:15:08 +02:00
Mose Müller
78b94d4fc8 feat: updating method docstring 2023-10-03 16:12:07 +02:00
Mose Müller
6d12371e7b docs: update installation instruction 2023-09-26 15:12:53 +02:00
Mose Müller
fb6146e01d docs: Updating links and formatting in Readme 2023-09-26 15:03:11 +02:00
8 changed files with 124 additions and 73 deletions

View File

@@ -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
View File

View File

@@ -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).

View File

@@ -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"

View File

@@ -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(

View File

@@ -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__

View 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,
}
}

View File

@@ -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
)