From f17ef315a148078515131c39a3be13c8fe7265da Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Vaidyanathan <62492557+VigneshVSV@users.noreply.github.com> Date: Sun, 22 Feb 2026 08:18:26 +0100 Subject: [PATCH 1/6] increase max size of footers --- mkdocs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index caf418d..032b559 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -271,11 +271,12 @@ extra: name: sponsor copyright: > - © BSD-3-Clause license, hololinked contributors. This website was written by humans, except API reference
- which can contain automated content. Leave us a star on +
+ © BSD-3-Clause license, hololinked contributors. This website was written by humans. Leave us a star on GitHub if you find this project useful!
This website uses anonymized privacy friendly GDPR compliant cookie-less analytics. +
extra_css: - stylesheets/extra.css From 38708b93efde90d0f0d2e546d425ed26a78449ef Mon Sep 17 00:00:00 2001 From: Vignesh Vaidyanathan Date: Sat, 7 Mar 2026 18:52:41 +0100 Subject: [PATCH 2/6] update doc --- .../articles/protocols/general.md | 41 ++++-- .../articles/protocols/http.md | 50 ++++++- .../articles/protocols/mqtt.md | 13 +- docs/beginners-guide/articles/security.md | 22 ++- docs/index.md | 4 +- docs/introduction/contributing.md | 27 ++-- docs/introduction/installation.md | 11 +- docs/introduction/use-cases.md | 25 +++- mkdocs.yml | 2 +- uv.lock | 129 +++++++++++++++--- 10 files changed, 255 insertions(+), 69 deletions(-) diff --git a/docs/beginners-guide/articles/protocols/general.md b/docs/beginners-guide/articles/protocols/general.md index fc6999e..1bcf333 100644 --- a/docs/beginners-guide/articles/protocols/general.md +++ b/docs/beginners-guide/articles/protocols/general.md @@ -1,30 +1,33 @@ # General -There are multiple ways to start serving your `Thing`: +There are multiple ways to start serving one's `Thing` instance based on the application requirements: -- One needs to run one `Thing` in multiple protocols -- One needs to run multiple `Thing`s in one protocol -- One needs to run multiple `Thing`s in multiple protocols +- One needs to run one `Thing` instance in multiple protocols +- One needs to run multiple `Thing` instances in one protocol +- One needs to run multiple `Thing`s instances in multiple protocols ## Run Single Thing in Multiple Protocols -There are two possible syntax options: +To configure a protocol server in a detailed fashion, instantiate them explicitly. +To start serving, one can pass them as an argument to the `run()` method of the `Thing`: -```python linenums="1" -from hololinked.server import HTTPServer, MQTTServer +```python linenums="1" title="Multiple Protocol Servers" +from hololinked.server import HTTPServer, MQTTPublisher, ZMQServer http_server = HTTPServer(port=9000) -mqtt_server = MQTTServer(host='mqtt.example.com', port=1883) +mqtt_server = MQTTPublisher(host='mqtt.example.com') zmq_server = ZMQServer(access_points=['IPC', 'tcp://*:9001']) Oscilloscope(id='oscilloscope').run(servers=[http_server, mqtt_server, zmq_server]) ``` -OR +Based on the protocol, the servers may support overriding the exposed configuration, for example, changing the HTTP URL path, or overriding an MQTT publishing worker. -```python linenums="1" -from hololinked.server import HTTPServer, MQTTServer +> The number of such features supported is generally a work in progress. Please consider reading the codebase if you wish to implement a highly specific feature, and also consider contributing your feature to the repository - [How to Contribute](https://docs.hololinked.dev/introduction/contributing/). +Alternatively, to quickly expose the `Thing` instance, supply the protocol name and the access point (port or address) as a tuple to the `run()` method. + +```python linenums="1" title="Quick Start" Oscilloscope(id='oscilloscope').run( access_points=( ('HTTP', 9000), @@ -34,10 +37,17 @@ Oscilloscope(id='oscilloscope').run( ) ``` -The first option is obviously preferred. +HTTP and ZMQ support a `run_with_` method: + +```python linenums="1" title="Quick Start Option 2" +Oscilloscope(id='oscilloscope').run_with_http_server(port=9000, ssl_context=ssl_context) +``` ## Run Multiple Things in One Protocol +All protocols support an `add_thing()` method that accepts a `Thing` instance +and a `run()` and `stop()` method that control their boot up & shutdown: + ```python linenums="1" from hololinked.server import HTTPServer @@ -50,14 +60,17 @@ server.run() ## Run Multiple Things in Multiple Protocols +Use the global `run()` method along with `server.add_thing()` to start any number of `Thing` instances in any number of protocols: + ```python linenums="1" -from hololinked.server import HTTPServer, MQTTServer, run +from hololinked.server import HTTPServer, MQTTPublisher, run http_server = HTTPServer(port=9000) -mqtt_server = MQTTServer(host='mqtt.example.com', port=1883) +mqtt_server = MQTTPublisher(host='mqtt.example.com') http_server.add_thing(Oscilloscope(id='oscilloscope')) mqtt_server.add_thing(DCPowerSupply(id='dc-power-supply')) run(servers=[http_server, mqtt_server]) +# HTTP server serves Oscilloscope and MQTTPublisher publishes events from DCPowerSupply ``` diff --git a/docs/beginners-guide/articles/protocols/http.md b/docs/beginners-guide/articles/protocols/http.md index 9d65ea2..58cbccc 100644 --- a/docs/beginners-guide/articles/protocols/http.md +++ b/docs/beginners-guide/articles/protocols/http.md @@ -1,5 +1,7 @@ # HTTP +[API Reference](../../../api-reference/protocols/http/index.md) + ## SSL context One can enable SSL for HTTP server by providing an SSL context while creating the server: @@ -25,11 +27,11 @@ Oscilloscope( ) ``` -## Register Custom Routes & Methods +## Routes & HTTP Methods -The default routes are created as follows: +The default URL path or HTTP routes are created as follows: -| Resource | Path | Description | Handler | Default Method | +| Resource | Path | Description | HTTP Handler | Default Method | | ----------------- | ------------------------------ | ---------------------------- | ------------------------- | -------------------------------------------------------------- | | Property | `//` | for property `foo_bar` | `PropertyHandler` | `GET` for read
`PUT` for write
`DELETE` for delete | | Action | `//` | for action `foo_bar` | `ActionHandler` | `POST` | @@ -37,9 +39,9 @@ The default routes are created as follows: | Thing Model | `//resources/wot-tm` | to get the Thing Model | - acts as property - | `GET` | | Thing Description | `//resources/wot-td` | to get the Thing Description | `ThingDescriptionHandler` | `GET` | -All underscores are converted to hyphens in the URL paths for every resource, and the Thing ID is a global prefix. +All python names underscores are converted to hyphens in the URL paths for every resource (property, action or event), and the Thing ID is a global prefix. -One can register custom routes and methods as follows: +One can register custom routes and HTTP methods as follows: ```python linenums="1" title="Custom Routes" from hololinked.server import HTTPServer @@ -47,14 +49,45 @@ from hololinked.server import HTTPServer server = HTTPServer(port=9000) server.add_property('/channels/data/A', Oscilloscope.channel_A) +server.add_action( + '/channels/data/A/with-offset', + Oscilloscope.get_channel_data_with_offset, + method='GET', +) server.add_event('/channels/data/A/stream', Oscilloscope.channel_A_data_event) Oscilloscope(id='oscilloscope').run(servers=[server]) ``` +## Path Parameters + +There are predefined URL path parameters with specific connotation: + +| Parameter | Format Example | Description | +|-----------|----------------|-------------| +| oneway invokation | `https://localhost:8080//?oneway=true` | invokes an action in a fire & forget fashion | +| noblock invokation | `https://localhost:8080//?noblock=true` | schedules an action and returns a message ID that can be used to retrieve the reply | +| fetch execution logs | `https://localhost:8080//?fetchExecutionLogs=true` | fetches the logs that were accumulated during the execution of an operation | +| invokation timeout | `https://localhost:8080//?invokationTimeout=7` | Creates a custom invokation timeout of a specific operation | +| execution timeout | `https://localhost:8080//?executionTimeout=7` | Creates a custom execution timeout of a specific operation | +| ignore errors for metadata generation | `https://localhost:8080//resources/wot-tm?ignore_errors=true` | ignore errors during Web of Things Thing Description or Thing Model generation to generate at least a partial working model | + +All path parameters can be combined. + +For `GET` requests, the payload needs to be specified as path parameters and the names should not overlap with the above specified fields. + +## Recognised Headers + +There are specific headers that are respected by the server: + +| Headers | Format Example | Description | +|---------|----------------|-------------| +| X-Message-ID | | Message ID to read reply for no block operation | +| X-API-Key, Authorization | | Used by security schemes | + ## Allow CORS -On the web browser, one may want to access the HTTP server from a different domain name, especially during development or in private networks. In such cases, one needs to enable CORS headers: +On the web browser, one may want to access the HTTP server from a different domain name, especially during development with `localhost` or in private networks. In such cases, one needs to enable CORS headers: ```python linenums="1" title="Enable CORS" http_server = HTTPServer(port=9000, config=dict(cors=True)) @@ -70,6 +103,11 @@ Oscilloscope( CORS headers are set only for authenticated clients. +> Note that for localhost, each port is considered a different domain. A web application and a server running on different ports on same machine will not be recognised to be in the same domain. + +!!! warning + CORS does not prevent execution of an operation on the server. It only prevents web browsers from reading the response of an HTTP request and is not a security mechanism in itself that can protect a HTTP server. + ## Remotely Stop If one wishes to remotely stop the HTTP server, one needs to exit both the served `Thing` instance as well as the server itself. This can be done as follows: diff --git a/docs/beginners-guide/articles/protocols/mqtt.md b/docs/beginners-guide/articles/protocols/mqtt.md index ab250e7..d7e2a00 100644 --- a/docs/beginners-guide/articles/protocols/mqtt.md +++ b/docs/beginners-guide/articles/protocols/mqtt.md @@ -1,8 +1,13 @@ # MQTT +[API Reference](../../../api-reference/protocols/mqtt/index.md) + +MQTT does not support request reply pattern in the current implementation of this package, +although the original v5 specification of the protocol might allow it. Consider using a second protocol like HTTP if your device can support it, for the time being. Contributions are welcome. + ## SSL context -To use MQTT over SSL/TLS, one needs to create an SSL context as follows: +To use MQTT over SSL/TLS, one could create an SSL context as follows: ```python linenums="1" title="Use SSL" import ssl, os @@ -22,4 +27,8 @@ Oscilloscope(id='oscilloscope').run(servers=[mqtt_server]) ``` Note that since MQTT has broker-based architecture and all publishers are clients to the broker, -the SSL context here is created with purpose `ssl.Purpose.SERVER_AUTH`. +the SSL context here is created with purpose `ssl.Purpose.SERVER_AUTH`. The certificate (say `ca.crt` above) must be pre-generated while deploying the MQTT broker. + +## Overiding Topic Name and QoS + +Not supported yet, see issue [here](https://github.com/hololinked-dev/hololinked/issues/134) \ No newline at end of file diff --git a/docs/beginners-guide/articles/security.md b/docs/beginners-guide/articles/security.md index 616ccdd..548e111 100644 --- a/docs/beginners-guide/articles/security.md +++ b/docs/beginners-guide/articles/security.md @@ -118,4 +118,24 @@ thing.run_with_http_server( ### OIDC Security Scheme -Coming soon. See issue [#87](https://github.com/hololinked-dev/hololinked/issues/87) +For frontend web applications that can support an authorization flow where a user in involved, one can use OIDC or OAuth2 flows to authenticate with the server. In this case, the `Thing` server is only a resource server and not an authorization server, which must be separately taken care by an authentication provider, like Keycloak or Google. + +Insantiate the `OIDCSecurityScheme` and supply your authorization server configuration: + +``` +oidc_security = OIDCSecurityScheme( + issuer=https://example.com, + audience='device-server' +) +thing = Thing(id="secure-thing") +thing.run_with_http_server( + port=9000, + security_scheme=oidc_security +) +``` + +The security scheme is called OIDC security scheme as it only validates logged in sessions and roles/scopes of the token issued. + +Implement the login flow on the client and supply the JWT bearer token in the Authorization header. + + diff --git a/docs/index.md b/docs/index.md index eef96fa..3314abd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,11 +5,11 @@ description: hololinked introduces SCADA & IoT systems to beginners # hololinked - Pythonic Object-Oriented Supervisory Control & Data Acquisition / Internet of Things -`hololinked` is a beginner-friendly pythonic tool suited for instrumentation control and data acquisition over network (IoT & SCADA). +`hololinked` is a beginner-friendly pythonic suited for instrumentation control and data acquisition over network (IoT & SCADA). As a novice, you have a requirement to control and capture data from your hardware, say in your electronics or science lab, and you want to show the data in a dashboard, provide a PyQt GUI or run automated scripts, `hololinked` can help. Even for isolated desktop applications or a small setup without networking, one can still separate the concerns of the tools that interact with the hardware & the hardware itself. -If you are a web developer or an industry professional looking for a web standards compatible (high-speed) IoT runtime, `hololinked` can be a decent choice. By conforming to [W3C Web of Things](https://www.w3.org/WoT/), one can expect a consistent API and flexible bidirectional message flow to interact with your devices, irrespective of the underlying protocol. Currently HTTP, MQTT & ZMQ are supported. +If you are a web developer or an industry professional looking for a web standards compatible, interoperable (high-speed) IoT runtime, `hololinked` can be a decent choice. By conforming to [W3C Web of Things](https://www.w3.org/WoT/), one can expect a consistent API and flexible bidirectional message flow to interact with your devices, irrespective of the underlying protocol. Currently HTTP, MQTT & ZMQ are supported. This implementation is based on RPC, built ground-up in python keeping both the latest web technologies and python principles in mind. diff --git a/docs/introduction/contributing.md b/docs/introduction/contributing.md index 4220495..97c08ed 100644 --- a/docs/introduction/contributing.md +++ b/docs/introduction/contributing.md @@ -25,22 +25,22 @@ For good first issues, visit repository wise: - [control panel](https://github.com/hololinked-dev/thing-control-panel/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) - [documentation](https://github.com/hololinked-dev/docs-v2/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) - [additional/new projects](https://github.com/hololinked-dev/.github/issues) -- [website](https://github.com/hololinked-dev/website/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) -- [kubernetes](https://github.com/hololinked-dev/vps-kubernetes-cluster/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) Our [contribution guidelines](https://github.com/hololinked-dev/hololinked/blob/main/CONTRIBUTING.md) may also help. There are also [weekly office hours](https://github.com/hololinked-dev#monthly-meetings) & [discord group](https://discord.com/invite/kEz87zqQXh) (currently no participants). +> Please do note that we are only looking for code contributions that can enfore code ownership and integrating contributors that can understand the codebase, not AI automated PRs. All solutions anybody can automate can also be automated by us, so, this is not the purpose of accepting contributions. + ## Setup Development Environment One can setup a development environment with [uv](https://docs.astral.sh/uv/) as follows: -1. Install uv if you don't have it already: https://docs.astral.sh/uv/getting-started/installation/ +1. Install uv if you don't have it already: [uv docs](https://docs.astral.sh/uv/getting-started/installation/) 2. Create and activate a virtual environment: ```bash -uv venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate +uv venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate ``` 3. Install the package in development mode with all dependencies: @@ -52,24 +52,15 @@ uv pip install -e ".[dev,test]" ## Running Tests -To run the tests with uv: - -In linux: - -```bash -uv run --active coverage run -m unittest discover -s tests -p 'test_*.py' -uv run --active coverage report -m -``` - -In windows: +To run unit and integration tests: ```bash -python -m unittest +pytest -s -v tests ``` ## Pre-commit Hooks -You can use pre-commit hooks to ensure code quality before committing changes, and be sure that certain pipeline checks will pass. +You can use pre-commit hooks to ensure code quality before committing changes, and be ensured that certain pipeline checks will pass. ```bash python -m pip install pre-commit @@ -79,7 +70,7 @@ pre-commit run --all-files Currently ruff, bandit and gitleaks are configured to run as pre-commit hooks. -To skip pre-commit hooks use: +Precommit hooks are optional. To skip them, use: ```bash git commit --no-verify -m "Your commit message" diff --git a/docs/introduction/installation.md b/docs/introduction/installation.md index 86da95a..9d2e6c9 100644 --- a/docs/introduction/installation.md +++ b/docs/introduction/installation.md @@ -2,16 +2,21 @@ From pip: - pip install hololinked +```sh +pip install hololinked +``` From conda: - conda install -c conda-forge hololinked +```sh +conda install -c conda-forge hololinked +``` One may also clone it from github & install directly (in develop mode): ```sh -git clone https://github.com/hololinked-dev/hololinked.git +git clone --no-recurse-submodules https://github.com/hololinked-dev/hololinked.git +# the submodules can be quite hefty and are not necessary cd hololinked pip install -e . ``` diff --git a/docs/introduction/use-cases.md b/docs/introduction/use-cases.md index edcb643..5fbff0f 100644 --- a/docs/introduction/use-cases.md +++ b/docs/introduction/use-cases.md @@ -3,6 +3,7 @@ Protocol Plausible Use Cases Operations + Underlying Implementation Security @@ -22,16 +23,20 @@ writeallproperties
properties and actions can be operated in a oneway and no-block manner (issue and query later format) as well + + [tornado]() + username-password,
device API key,
IP filter,
- OAuth2 OIDC (experimental) + OAuth2 OIDC ZMQ TCP Networked Control Systems, subnet protected containerized apps like in Kubernetes + [pyzmq]() username-password planned,
device API key planned @@ -65,6 +70,9 @@ subscribeevent,
unsubscribeevent + + [aiomqtt]() + username-password,
TLS with client certificates (you set this up in the broker anyway) @@ -86,4 +94,19 @@ TLS with client certificates (you set this up in the broker anyway) + + CoAP + + Planned, April 2026. + + + Will be updated + + + [aiocoap]() + + + Will be updated + + diff --git a/mkdocs.yml b/mkdocs.yml index 032b559..346cf0d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,10 +8,10 @@ nav: - Home: - Introduction: index.md - Installation: introduction/installation.md + - Protocol Use Cases: introduction/use-cases.md - Resources: introduction/resources.md - Contributing: introduction/contributing.md - License: introduction/license.md - - Protocol Use Cases: introduction/use-cases.md - Security Scan Overview: introduction/security-scanning.md - Handbook: - Expose Python Objects: beginners-guide/articles/servers.md diff --git a/uv.lock b/uv.lock index 57bd209..7d862e5 100644 --- a/uv.lock +++ b/uv.lock @@ -239,24 +239,47 @@ wheels = [ [[package]] name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, ] [[package]] @@ -320,6 +343,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/df/1514580907b0bac0970415e5e24ef96a9c1fa71dcf2aa0139045b58fae9a/configparser-7.1.0-py3-none-any.whl", hash = "sha256:98e374573c4e10e92399651e3ba1c47a438526d633c44ee96143dec26dad4299", size = 17074 }, ] +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289 }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637 }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742 }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528 }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993 }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855 }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635 }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038 }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181 }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482 }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497 }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819 }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230 }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909 }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287 }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728 }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287 }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291 }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539 }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199 }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131 }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072 }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170 }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741 }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728 }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001 }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637 }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487 }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514 }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349 }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980 }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143 }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674 }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801 }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755 }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539 }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794 }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160 }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123 }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220 }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050 }, +] + [[package]] name = "debugpy" version = "1.8.17" @@ -496,12 +572,13 @@ wheels = [ [[package]] name = "hololinked" -version = "0.3.8" -source = { git = "https://github.com/hololinked-dev/hololinked.git?branch=v0.3-global-config-cleanup#28fdb0f4c43d674dd46b7cf98503a9f3ea5d3599" } +version = "0.3.11" +source = { git = "https://github.com/hololinked-dev/hololinked.git?branch=main#7b8c98dea1bfe82eb6402e37aabd177863f22029" } dependencies = [ { name = "aiomqtt" }, { name = "argon2-cffi" }, { name = "bcrypt" }, + { name = "cryptography" }, { name = "fastjsonschema" }, { name = "httpx" }, { name = "ifaddr" }, @@ -509,6 +586,7 @@ dependencies = [ { name = "msgspec" }, { name = "psycopg2-binary" }, { name = "pydantic" }, + { name = "pyjwt" }, { name = "pymongo" }, { name = "pyzmq" }, { name = "serpent" }, @@ -582,7 +660,7 @@ dev = [ { name = "ruff", specifier = ">=0.12.10" }, { name = "serpent", specifier = "==1.41" }, ] -repo = [{ name = "hololinked", git = "https://github.com/hololinked-dev/hololinked.git?branch=v0.3-global-config-cleanup" }] +repo = [{ name = "hololinked", git = "https://github.com/hololinked-dev/hololinked.git?branch=main" }] [[package]] name = "httpcore" @@ -1723,6 +1801,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224 }, +] + [[package]] name = "pymdown-extensions" version = "10.15" From 4ce9650ba94af102c279a8e020f3410c344d4060 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Vaidyanathan <62492557+VigneshVSV@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:37:21 +0100 Subject: [PATCH 3/6] update docs again --- docs/beginners-guide/articles/object-proxy.md | 70 +++++++++---------- .../articles/protocols/general.md | 8 +-- .../articles/protocols/http.md | 4 +- .../articles/protocols/mqtt.md | 5 +- docs/beginners-guide/articles/security.md | 39 +++++------ .../beginners-guide/articles/serialization.md | 28 ++++---- docs/beginners-guide/articles/servers.md | 58 ++++++--------- .../beginners-guide/articles/state-machine.md | 40 +++++------ docs/beginners-guide/articles/thing.md | 7 +- docs/index.md | 11 +-- docs/introduction/contributing.md | 11 +-- docs/introduction/installation.md | 2 +- docs/introduction/resources.md | 4 +- docs/introduction/use-cases.md | 58 +++++---------- docs/stylesheets/extra.css | 10 +-- mkdocs.yml | 1 - 16 files changed, 153 insertions(+), 203 deletions(-) diff --git a/docs/beginners-guide/articles/object-proxy.md b/docs/beginners-guide/articles/object-proxy.md index 954d86a..3d4f273 100644 --- a/docs/beginners-guide/articles/object-proxy.md +++ b/docs/beginners-guide/articles/object-proxy.md @@ -2,19 +2,28 @@ [API Reference](../../api-reference/clients/object-proxy.md) -`Thing` objects can be consumed using an `ObjectProxy` instance, per protocol, where the interactions with -a property, action or event can be abstracted as operations like: +`ObjectProxy` is a procedural client meant to consume a `Thing` instance where the interactions with a property, action or event can be abstracted as operations like: - Read/Write/Observe Property - Invoke Action - Subscribe/Unsubscribe Event -Further, one would require a [Thing Description](https://www.w3.org/TR/wot-thing-description11/#introduction-td) to construct the client. -The `Thing Description` contains the metadata of the `Thing` like available properties, actions and events, their data types, -forms (protocols and endpoints) etc. which can be used to create the `ObjectProxy`. In `hololinked`, the `Thing Description` is -automatically generated and served by the server protocols, and there is lesser requirement for manual intervention. +One would require a [Thing Description](https://www.w3.org/TR/wot-thing-description11/#introduction-td) to construct the client. The `ThingDescription` contains the metadata of the `Thing` like available properties, actions and events, their data types, their protocols and endpoints (called `forms`), among other metadata, in JSON format. -To instantiate an `ObjectProxy`, use the `ClientFactory`: +!!! Info + + See some online hosted examples at the [examples website](https://examples.hololinked.dev) - + [Camera](https://examples.hololinked.dev/simulations/camera/resources/wot-td?ignore_errors=true) | + [Spectrometer](https://examples.hololinked.dev/simulations/spectrometer/resources/wot-td) | + [Oscilloscope](https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td)
+ +This metadata is used to create the `ObjectProxy`, and in `hololinked`, this JSON document is automatically generated and served by the server protocols. There is lesser requirement for manually creating said `ThingDescription`, unless one wants to highly customize it. The purpose of this JSON metadata is to provide both a human and machine readable description of the `Thing` and its capabilities, so that clients can automatically discover and interact with it without prior knowledge. Sounds like something useful for AI applications right? + +To instantiate an `ObjectProxy`, use the `ClientFactory` for one protocol at a time: + +!!! Note + + Only one protocol is allowed per `ObjectProxy` client. You can always create multiple clients if you need multiple protocols. === "HTTP" @@ -23,8 +32,8 @@ To instantiate an `ObjectProxy`, use the `ClientFactory`: thing = ClientFactory.http(url="http://localhost:8000/my-thing/resources/wot-td") ``` - One needs to append `/resources/wot-td` to the URL to load a `Thing Description`, the reason being that if one - stores pregenerated Thing Descriptions in a different location, one can still load them. + For HTTP, one needs to append `/resources/wot-td` to the URL to load an automatically generated `Thing Description` + from the HTTP server serving the `Thing`. === "ZMQ" @@ -50,11 +59,11 @@ To instantiate an `ObjectProxy`, use the `ClientFactory`: ) ``` - When using ZMQ-TCP, on the server side one may specify the address as `access_point="tcp://*:5555"`. - On the client side, however, one must use the explicit address, like `access_point="tcp://my-raspberry-pi:5555"` or - `access_point="tcp://localhost:5555"`. + For ZMQ, one needs to specify the `server_id`, `thing_id` and the `access_point` (say, `TCP` or `IPC`) where the server is accessible. These values are customizable while instantiating an instance of the [`ZMQServer`](../articles/protocols/general.md). If the `run()` method on the `Thing` instance was used, the `server_id` defaults to `thing_id`. + + When using ZMQ-TCP, on the server side one may specify the address as `access_point="tcp://*:5555"` to bind on all interfaces. On the client side, however, one must use the explicit address containing the machine hostname, like `access_point="tcp://my-raspberry-pi:5555"` or `access_point="tcp://localhost:5555"`. - The `Thing Description` is fetched automatically from the server for ZMQ transport. + The `Thing Description` is fetched automatically from the server while mediating the connection. === "MQTT" @@ -69,14 +78,10 @@ To instantiate an `ObjectProxy`, use the `ClientFactory`: ) ``` - MQTT usually supports only pub-sub or event based interactions. Therefore, only event subscriptions - are supported on the `ObjectProxy` and properties and actions raise `AttributeError`. - On subscription, the broker should publish a `Thing Description` to the topic `/thing-description` - so that the client can find other available events. + The `Thing Description` is published to the MQTT Broker under the topic `/thing-description` by the server, + and the `ClientFactory` subsribes to the `Thing Description` and constructs the `ObjectProxy`. -!!! Note - - Only one protocol is allowed per client. + MQTT currently supports only events and properties that publish change events. ### read and write properties @@ -108,10 +113,7 @@ One can also use `invoke_action` to invoke an action by name ### oneway scheduling -`oneway` scheduling do not fetch return value and exceptions that might occur while executing a property or an action. -The server schedules the operation and returns an empty response to the client, allowing it to process further logic. -It is possible to set a property, set multiple or all properties or invoke an action in -oneway. Other operations are not supported. +`oneway` scheduling do not fetch return value and exceptions that might occur while executing a property or an action. The server schedules the operation and returns an empty response to the client, allowing it to process further logic. It is possible to set a property, set multiple or all properties or invoke an action in oneway. Other operations are not supported. ```py title="oneway=True" linenums="1" --8<-- "docs/beginners-guide/code/object_proxy/sync.py:79:103" @@ -119,12 +121,11 @@ oneway. Other operations are not supported. Simply provide the keyword argument `oneway=True` to the operation method. -Importantly, one cannot have an action argument or a property on the server named `oneway` as it is a -reserved keyword argument to such methods on the client. At least they become inaccessible on the `ObjectProxy`. +`oneway` must be always specified as a keyword argument. Due to this reason, one cannot have an action argument or a property on the server named `oneway` as it is a reserved keyword argument to such methods on the client. At least they become inaccessible on the `ObjectProxy`. ### no-block scheduling -`noblock` allows scheduling a property or action but collecting the reply later: +`noblock` allows scheduling a property or action and collecting the reply later: ```py title="noblock=True" linenums="1" --8<-- "docs/beginners-guide/code/object_proxy/sync.py:107:139" @@ -156,7 +157,7 @@ Simply prefix `async_` to the method name, like `async_read_property`, `async_wr There is no support for dot operator based access for asyncio. One may also note that `async` operations do not change the nature of the execution on the server side. `asyncio` on `ObjectProxy` is purely a client-side non-blocking network call, so that one can -simultaneously perform other async operations while the client is waiting for the network operation to complete. +simultaneously perform other async operations while the client is waiting for said network operation to complete. !!! Note @@ -214,15 +215,14 @@ Once again, to customize callback scheduling, see [events section](./events.md#s ##### foreign attributes on client -Normally, there cannot be user defined attributes on the `ObjectProxy` as the attributes on the client -must mimic the available properties, actions and events on the server. An accidental setting of an unknown -property must raise an `AttributeError` when not found on the server, instead of silently going through and setting -said property on the client object itself: +Normally, there cannot be user defined attributes on the `ObjectProxy` as the attributes on the client must mimic the available properties, actions and events on the server. An accidental setting of an unknown property must raise an `AttributeError`, when not found on the server, instead of silently setting said property on the client itself: ```py title="foreign attributes raise AttributeError" linenums="1" --8<-- "docs/beginners-guide/code/object_proxy/customizations.py:3:7" ``` +The requirement for this behaviour is due to python's duck typing. If one intends to set a property named `foo`, and instead types it as `fooo` (misspelt), it is better to raise an error instead of silently setting a new attribute `fooo` on the client. + One can overcome this by setting `allow_foreign_attributes` to `True`: ```py title="foreign attributes allowed" linenums="1" @@ -236,15 +236,13 @@ For invoking any operation (say property read/write & action call), two types of - `invokation_timeout` - the amount of time the server has to wait for an operation to be scheduled - `execution_timeout` - the amount of time the server has to complete the operation once scheduled -When the `invokation_timeout` expires, the operation is guaranteed to be never scheduled. When the `execution_timeout` expires, the operation is scheduled but returns without the expected response. In both cases, a `TimeoutError` is raised on the client side specifying the timeout type. If an operation is scheduled but not completed within the `execution_timeout`, the server may still complete the operation and there can be unknown side effects or client does not know about it. +When the `invokation_timeout` expires, the operation is guaranteed to be never executed. When the `execution_timeout` expires, the operation is scheduled but returns without the expected response. In both cases, a `TimeoutError` is raised on the client specifying the timeout type. If an operation is scheduled but not completed within the `execution_timeout`, the server may still complete the operation but client does not know about it. ```py title="timeout specification" linenums="1" --8<-- "docs/beginners-guide/code/object_proxy/customizations.py:29:36" ``` -!!! Note - - Currently only a global specification is supported. In future, one may be able to specify timeouts per operation. +> Currently only a global customization of these values are supported. In future, one may be able to specify timeouts per operation. #### Remote Access to Logger diff --git a/docs/index.md b/docs/index.md index 3314abd..c22fc20 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ description: hololinked introduces SCADA & IoT systems to beginners # hololinked - Pythonic Object-Oriented Supervisory Control & Data Acquisition / Internet of Things -`hololinked` is a beginner-friendly pythonic suited for instrumentation control and data acquisition over network (IoT & SCADA). +`hololinked` is a beginner-friendly, extensible pythonic tool suited for instrumentation control and data acquisition over network (IoT & SCADA). As a novice, you have a requirement to control and capture data from your hardware, say in your electronics or science lab, and you want to show the data in a dashboard, provide a PyQt GUI or run automated scripts, `hololinked` can help. Even for isolated desktop applications or a small setup without networking, one can still separate the concerns of the tools that interact with the hardware & the hardware itself. @@ -67,7 +67,7 @@ Each device, or **Thing**, is modeled in software with: - **Actions**: Methods that command the hardware to perform operations. - _Oscilloscope_ - connect/disconnect hardware - - _Camera_ - start/stop measurement or video capture + - _Camera_ - snap image or start/stop video capture - _DC Power Supply_ - execute control routines (e.g., closed-loop control) - **Events**: Asynchronous messages or data streams to clients (e.g., alarms, measured values) @@ -82,7 +82,9 @@ This separation is independent of: - data serialization or binary representation (JSON, MessagePack, Pickle etc.) - security or access control mechanisms (JWT, Basic Auth, OAuth etc.) -The `Thing` object represents the physical device and is modeled as a class, encapsulating its properties, actions, and events as its attributes & methods. Additionally, **state machines** can constrain property and action execution: +The `Thing` object (as in OOP) represents the physical device and is modeled as a class, encapsulating its properties, actions, and events as its attributes & methods. + +Additionally, **state machines** can constrain property and action execution: - _Oscilloscope_ - Cannot start a new measurement while one is ongoing - _Camera_ - Cannot change exposure time while capturing video @@ -90,4 +92,5 @@ The `Thing` object represents the physical device and is modeled as a class, enc --- > **Ready to get started?** -> See the [Handbook](beginners-guide/articles/servers) section for concepts and code example and the [Examples Repository](https://github.com/hololinked-dev/examples) section for hardware-specific implementations. +> See the [Handbook](beginners-guide/articles/servers) section for concepts and code example and the [Examples Repository](https://github.com/hololinked-dev/examples) section for hardware-specific implementations. There are some online (live) examples available listed on the [project website](https://hololinked.dev). + diff --git a/docs/introduction/contributing.md b/docs/introduction/contributing.md index 97c08ed..1dde945 100644 --- a/docs/introduction/contributing.md +++ b/docs/introduction/contributing.md @@ -37,27 +37,28 @@ One can setup a development environment with [uv](https://docs.astral.sh/uv/) as 1. Install uv if you don't have it already: [uv docs](https://docs.astral.sh/uv/getting-started/installation/) 2. Create and activate a virtual environment: - ```bash uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate ``` 3. Install the package in development mode with all dependencies: - ```bash -uv pip install -e . +uv sync +# or +uv sync --no-install-project uv pip install -e ".[dev,test]" ``` ## Running Tests To run unit and integration tests: - ```bash pytest -s -v tests ``` +You might need docker daemon for running some tests (currently `test_16_protocols_mqtt`). + ## Pre-commit Hooks You can use pre-commit hooks to ensure code quality before committing changes, and be ensured that certain pipeline checks will pass. @@ -68,7 +69,7 @@ pre-commit install pre-commit run --all-files ``` -Currently ruff, bandit and gitleaks are configured to run as pre-commit hooks. +Currently `ruff`, `bandit` and `gitleaks` are configured to run as pre-commit hooks. Precommit hooks are optional. To skip them, use: diff --git a/docs/introduction/installation.md b/docs/introduction/installation.md index 9d2e6c9..c21444a 100644 --- a/docs/introduction/installation.md +++ b/docs/introduction/installation.md @@ -24,7 +24,7 @@ pip install -e . With `uv`: ```sh -git clone https://github.com/hololinked-dev/hololinked.git +git clone --no-recurse-submodules https://github.com/hololinked-dev/hololinked.git cd hololinked uv venv source .venv/bin/activate # for Linux/Mac diff --git a/docs/introduction/resources.md b/docs/introduction/resources.md index f8894c0..5217cf5 100644 --- a/docs/introduction/resources.md +++ b/docs/introduction/resources.md @@ -7,7 +7,7 @@ Check out: | Repository | URL | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | examples | [GitHub](https://github.com/hololinked-dev/examples.git) | repository containing example code discussed in this documentation | -| control panel | [Website](https://control-panel.hololinked.dev) [GitHub](https://github.com/hololinked-dev/thing-control-panel.git) | GUI to view your devices' properties, actions, and events | -| Infrastructure Components | [GitHub](https://github.com/hololinked-dev/daq-system-infrastructure.git) | Databases, MQTT broker (Identity System in the future), based on docker | +| control panel | [Website](https://control-panel.hololinked.dev) [GitHub](https://github.com/hololinked-dev/thing-control-panel.git) | GUI to view and interact with your devices' properties, actions, and events | +| Infrastructure Components | [GitHub](https://github.com/hololinked-dev/daq-system-infrastructure.git) | Databases, MQTT broker, Identity System (Keycloak), based on docker | | Eclipse ThingWeb | [GitHub](https://github.com/eclipse-thingweb/node-wot) | javascript implementation of the W3C Web of Things (WoT) standard, for example, to use as HTTP clients | | TD Editor | [Website](https://eclipse.github.io/editdor/) [GitHub](https://github.com/eclipse/editdor) | GUI to create and edit Thing Descriptions (TD) | diff --git a/docs/introduction/use-cases.md b/docs/introduction/use-cases.md index 5fbff0f..a45ecec 100644 --- a/docs/introduction/use-cases.md +++ b/docs/introduction/use-cases.md @@ -9,22 +9,11 @@ HTTP Web Apps - - readproperty,
- writeproperty,
- observeproperty,
- unobserveproperty,
- invokeaction,
- subscribeevent,
- unsubscribeevent,
- readmultipleproperties,
- writemultipleproperties,
- readallproperties,
- writeallproperties
- properties and actions can be operated in a oneway and no-block manner (issue and query later format) as well + + Properties,
Actions,
Events - [tornado]() + tornado username-password,
@@ -36,19 +25,17 @@ ZMQ TCP Networked Control Systems, subnet protected containerized apps like in Kubernetes - [pyzmq]() - - username-password planned,
- device API key planned + + Properties,
Actions,
Events + + pyzmq + + planned, likely may take upto end of 2026, please use HTTP if needed. Its not slow. ZMQ IPC Desktop Applications, Python Dashboards without exposing device API directly on network - - username-password planned,
- device API key planned - ZMQ INPROC @@ -62,18 +49,15 @@ MQTT - Reliable pub-sub & incorporating into existing systems that use MQTT for
lightweight messaging + Reliable pub-sub & incorporating into existing systems that use MQTT for lightweight messaging - - observeproperty,
- unobserveproperty,
- subscribeevent,
- unsubscribeevent + + Properties that emit change events,
plain Events - [aiomqtt]() + aiomqtt/Eclipse Paho - + username-password,
TLS with client certificates (you set this up in the broker anyway) @@ -81,17 +65,7 @@ MQTT with websockets - Reliable pub-sub for web applications, planned for March 2026 release. - - - observeproperty,
- unobserveproperty,
- subscribeevent,
- unsubscribeevent - - - username-password,
- TLS with client certificates (you set this up in the broker anyway) + planned for April/May 2026 release. @@ -103,7 +77,7 @@ Will be updated - [aiocoap]() + aiocoap Will be updated diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 600823f..69e9596 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -126,12 +126,4 @@ /* Optional polish */ .md-nav__link--active { font-weight: 700; } /* make current item pop */ -.md-nav__link code { font-family: inherit; } /* keep code fragments consistent in TOCs */ - -/* Compact footer navigation (prev/next links) */ -.md-footer__inner { - padding-top: 0rem; - padding-bottom: 0rem; - max-height: 60px; -} - +.md-nav__link code { font-family: inherit; } /* keep code fragments consistent in TOCs */ \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 346cf0d..626060d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,7 +79,6 @@ nav: - Clients: - ClientFactory: api-reference/clients/base.md - ObjectProxy: api-reference/clients/object-proxy.md - # Security - Abstractions: - ConsumedThingAction: api-reference/clients/bases/cta.md - ConsumedThingProperty: api-reference/clients/bases/ctp.md From 896983852fda65b2c4cc2575ecf157830e01b4e7 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Vaidyanathan <62492557+VigneshVSV@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:51:22 +0100 Subject: [PATCH 4/6] update serialization doc --- .../beginners-guide/articles/serialization.md | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/beginners-guide/articles/serialization.md b/docs/beginners-guide/articles/serialization.md index c12f46e..e58e9cd 100644 --- a/docs/beginners-guide/articles/serialization.md +++ b/docs/beginners-guide/articles/serialization.md @@ -1,20 +1,21 @@ # Serializers To promote interoperability, safety and ease of integration with web applications, the default content type for all properties, actions and events is the JSON data format - `application/json`. -Moreover, a `C++` implementation of JSON (`msgspec`) is used, therefore one need not worry about the performance for a large number of use cases, including lists of 1000 to 10000 floats +The default included implementation relies on `C++` (`msgspec`), therefore one need not worry about the performance for a large number of use cases, including lists of 1000 to 10000 floats and large nested dictionaries or objects. ### Changing the Default Serializer -It is possible to change the serialization format on an individual basis by using the `Serializers` singleton. -Set the desired serialization on the specific property, action or event: +One would use the `Serializers` singleton to set the desired serialization on the specific property, action or event to overcome the default setting on an individual basis: -```py linenums="1" title="Change Serializer for Specific Properties, Actions or Events" hl_lines="8 12" +```py linenums="1" title="Change Serializer for Specific Properties, Actions or Events" hl_lines="4 10 14" from hololinked.serializers import Serializers # Using a pickle serializer for a list property Serializers.register_for_object(OceanOpticsSpectrometer.spectrum, Serializers.pickle) # pickle is not recommended, use message pack if possible +# from hololinked.config import global_config +# global_config.ALLOW_PICKLE = True # default False # Using message pack for an action or event Serializers.register_for_object( @@ -29,9 +30,9 @@ Serializers.register_for_object( OceanOpticsSpectrometer(id="spectro1").run_with_http_server() ``` -Other properties, actions or events will still use the default (JSON) serialization. By referring the property, action or event at the class level, the data format change will be reflected for all instances. To overload the content type per `Thing` instance, specify the `thing_id` as well: +i.e., other properties, actions or events will still use the default (JSON) serialization. By referring the property, action or event at the class level, the data format change will be reflected for all instances. To overload the content type per `Thing` instance, specify the `thing_id` as well: -```py linenums="1" title="Overload per Thing instance" hl_lines="14 15" +```py linenums="1" title="Overload per Thing instance" hl_lines="13 15" from hololinked.serializers import Serializers spectrometer = OceanOpticsSpectrometer(id='spectro1') @@ -53,9 +54,9 @@ Serializers.register_for_object_per_thing_instance( spectrometer.run(...) ``` -To overload the default serializer altogether for a `Thing` **instance**: +To overload the default serializer for the entire `Thing` **instance** altogether: -```py linenums="1" title="default serializer for thing instance" hl_lines="7 8" +```py linenums="1" title="default serializer for thing instance" hl_lines="6-8" from hololinked.serializers import Serializers spectrometer = OceanOpticsSpectrometer(id='spectro1') @@ -66,7 +67,8 @@ Serializers.register_for_thing_instance( serializer=Serializers.msgpack ) -# specific property will use pickle, other properties, actions and events will use msgpack +# specific property will use pickle, other properties, actions +# and events will use msgpack Serializers.register_for_object_per_thing_instance( thing_id=spectrometer.id, objekt=OceanOpticsSpectrometer.spectrum.name, # accepts only string name @@ -76,7 +78,14 @@ Serializers.register_for_object_per_thing_instance( spectrometer.run(...) ``` -per-instance overloads have higher priority than the per-Thing-object. The singleton behaviour of `Serializers` ensures that all registrations are available across all protocol servers within the same process. +The order of priority is as follows (from highest to lowest): + +1. Per-Thing-instance overloads for specific properties, actions or events. +2. Per-Thing-instance overloads for the entire Thing instance. +3. Per-Thing-class overloads for specific properties, actions or events. +4. Fallback to the default serializer. It is possible to change the default serializer, see API reference. + +The singleton behaviour of `Serializers` ensures that all registrations are available across all protocol servers within the same process. ### Built-in Serializers From 0e68a72623ce6215ec644675b44f73b162425740 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Vaidyanathan <62492557+VigneshVSV@users.noreply.github.com> Date: Fri, 15 May 2026 13:00:40 +0200 Subject: [PATCH 5/6] generic updates --- docs/api-reference/clients/bases/utils.md | 2 +- docs/api-reference/clients/security/apikey.md | 8 ++++++++ docs/api-reference/clients/security/basic.md | 7 +++++++ docs/api-reference/clients/security/oidc.md | 6 ++++++ docs/api-reference/td/form.md | 3 +++ docs/api-reference/td/metadata.md | 5 +++++ docs/api-reference/td/security.md | 11 +++++++++++ docs/beginners-guide/articles/object-proxy.md | 2 +- docs/index.md | 16 ++++++++++++++-- docs/introduction/installation.md | 1 + docs/introduction/use-cases.md | 10 +++++++--- mkdocs.yml | 9 ++++++++- pyproject.toml | 2 +- 13 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 docs/api-reference/clients/security/apikey.md create mode 100644 docs/api-reference/clients/security/basic.md create mode 100644 docs/api-reference/clients/security/oidc.md create mode 100644 docs/api-reference/td/form.md create mode 100644 docs/api-reference/td/metadata.md create mode 100644 docs/api-reference/td/security.md diff --git a/docs/api-reference/clients/bases/utils.md b/docs/api-reference/clients/bases/utils.md index 0741d09..5de8baa 100644 --- a/docs/api-reference/clients/bases/utils.md +++ b/docs/api-reference/clients/bases/utils.md @@ -1,3 +1,3 @@ -::: hololinked.client.abstractions.raise_local_exception \ No newline at end of file + \ No newline at end of file diff --git a/docs/api-reference/clients/security/apikey.md b/docs/api-reference/clients/security/apikey.md new file mode 100644 index 0000000..fed13c9 --- /dev/null +++ b/docs/api-reference/clients/security/apikey.md @@ -0,0 +1,8 @@ + + +::: hololinked.client.security.APIKeySecurity + options: + members: + - __init__ + - value + - http_header_name \ No newline at end of file diff --git a/docs/api-reference/clients/security/basic.md b/docs/api-reference/clients/security/basic.md new file mode 100644 index 0000000..1a6bcbd --- /dev/null +++ b/docs/api-reference/clients/security/basic.md @@ -0,0 +1,7 @@ + + +::: hololinked.client.security.BasicSecurity + options: + members: + - __init__ + - http_header diff --git a/docs/api-reference/clients/security/oidc.md b/docs/api-reference/clients/security/oidc.md new file mode 100644 index 0000000..5775c89 --- /dev/null +++ b/docs/api-reference/clients/security/oidc.md @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/docs/api-reference/td/form.md b/docs/api-reference/td/form.md new file mode 100644 index 0000000..bb251f9 --- /dev/null +++ b/docs/api-reference/td/form.md @@ -0,0 +1,3 @@ + + +::: hololinked.td.forms.Form \ No newline at end of file diff --git a/docs/api-reference/td/metadata.md b/docs/api-reference/td/metadata.md new file mode 100644 index 0000000..8164bc9 --- /dev/null +++ b/docs/api-reference/td/metadata.md @@ -0,0 +1,5 @@ + + +::: hololinked.td.metadata.Link + +::: hololinked.td.metadata.VersionInfo \ No newline at end of file diff --git a/docs/api-reference/td/security.md b/docs/api-reference/td/security.md new file mode 100644 index 0000000..99a36e4 --- /dev/null +++ b/docs/api-reference/td/security.md @@ -0,0 +1,11 @@ + + +::: hololinked.td.security_definitions.SecurityScheme + +::: hololinked.td.security_definitions.NoSecurityScheme + +::: hololinked.td.security_definitions.BasicSecurityScheme + +::: hololinked.td.security_definitions.APIKeySecurityScheme + +::: hololinked.td.security_definitions.OIDCSecurityScheme \ No newline at end of file diff --git a/docs/beginners-guide/articles/object-proxy.md b/docs/beginners-guide/articles/object-proxy.md index 3d4f273..d714d4a 100644 --- a/docs/beginners-guide/articles/object-proxy.md +++ b/docs/beginners-guide/articles/object-proxy.md @@ -17,7 +17,7 @@ One would require a [Thing Description](https://www.w3.org/TR/wot-thing-descript [Spectrometer](https://examples.hololinked.dev/simulations/spectrometer/resources/wot-td) | [Oscilloscope](https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td)
-This metadata is used to create the `ObjectProxy`, and in `hololinked`, this JSON document is automatically generated and served by the server protocols. There is lesser requirement for manually creating said `ThingDescription`, unless one wants to highly customize it. The purpose of this JSON metadata is to provide both a human and machine readable description of the `Thing` and its capabilities, so that clients can automatically discover and interact with it without prior knowledge. Sounds like something useful for AI applications right? +The purpose of this JSON metadata is to provide both a human and machine readable description of the `Thing` and its capabilities, so that clients can automatically discover and interact with it without prior knowledge. In `hololinked`, this JSON document is automatically generated and served by the server protocols. There is lesser requirement for manually creating said `ThingDescription`, unless one wants to highly customize it. To instantiate an `ObjectProxy`, use the `ClientFactory` for one protocol at a time: diff --git a/docs/index.md b/docs/index.md index c22fc20..27d0cb4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -91,6 +91,18 @@ Additionally, **state machines** can constrain property and action execution: --- -> **Ready to get started?** -> See the [Handbook](beginners-guide/articles/servers) section for concepts and code example and the [Examples Repository](https://github.com/hololinked-dev/examples) section for hardware-specific implementations. There are some online (live) examples available listed on the [project website](https://hololinked.dev). +!!! tip "Ready to get started?" + Please see: + + - [Handbook](beginners-guide/articles/servers) section for concepts + - Code example and the [Examples Repository](https://github.com/hololinked-dev/examples) section for hardware-specific implementations + - Online (live) examples listed on the [project website](https://hololinked.dev) + + Office hours are available on request for any formal or informal discussions, help with implementation or just to say hi: + + - Every Wednesday 4-6PM CET/CEST + - Every Friday 9-10AM CET/CEST, 4-5PM CET/CEST + + Please email to info@hololinked.dev. You can also ask questions in the [discord group](https://discord.com/invite/kEz87zqQXh) (currently no participants). + diff --git a/docs/introduction/installation.md b/docs/introduction/installation.md index c21444a..4fbada0 100644 --- a/docs/introduction/installation.md +++ b/docs/introduction/installation.md @@ -9,6 +9,7 @@ pip install hololinked From conda: ```sh +pip install aiomqtt conda install -c conda-forge hololinked ``` diff --git a/docs/introduction/use-cases.md b/docs/introduction/use-cases.md index a45ecec..c5b1a78 100644 --- a/docs/introduction/use-cases.md +++ b/docs/introduction/use-cases.md @@ -24,7 +24,7 @@ ZMQ TCP - Networked Control Systems, subnet protected containerized apps like in Kubernetes + Networked Control Systems, devices in protected networks, containerized apps like in Kubernetes Properties,
Actions,
Events @@ -35,7 +35,7 @@ ZMQ IPC - Desktop Applications, Python Dashboards without exposing device API directly on network + Desktop Applications, Python Dashboards without exposing device API directly on network (streamlit, dash, panel etc.) ZMQ INPROC @@ -69,7 +69,11 @@ - CoAP + + CoAP
+ CoAP Websockets
+ CoAP UDP + Planned, April 2026. diff --git a/mkdocs.yml b/mkdocs.yml index 626060d..1b4afa4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,10 @@ nav: - ConsumedThingEvent: api-reference/clients/bases/cte.md - SSE: api-reference/clients/bases/sse.md - Utils: api-reference/clients/bases/utils.md + - Security: + - Basic: api-reference/clients/security/basic.md + - API Key: api-reference/clients/security/apikey.md + - OIDC Direct Access Grant: api-reference/clients/security/oidc.md - Servers: - HTTP: api-reference/protocols/http/index.md - ZMQ: api-reference/protocols/zmq/index.md @@ -103,6 +107,9 @@ nav: - EventAffordance: api-reference/td/interaction_affordance/event_affordance.md - DataSchema: api-reference/td/data_schema.md - Thing Model: api-reference/td/tm.md + - Form: api-reference/td/form.md + - Metadata: api-reference/td/metadata.md + - Security Definitions: api-reference/td/security.md - Bases: - Schema: api-reference/td/bases/schema.md # Examples @@ -271,7 +278,7 @@ extra: copyright: >
- © BSD-3-Clause license, hololinked contributors. This website was written by humans. Leave us a star on + © BSD-3-Clause license, hololinked contributors. Leave us a star on GitHub if you find this project useful!
This website uses anonymized privacy friendly GDPR compliant cookie-less analytics. diff --git a/pyproject.toml b/pyproject.toml index d2a0198..3f90891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,4 +36,4 @@ repo = [ ] [tool.uv.sources] -hololinked = { git = "https://github.com/hololinked-dev/hololinked.git", branch = "main" } \ No newline at end of file +hololinked = { git = "https://github.com/hololinked-dev/hololinked.git", branch = "mypy" } \ No newline at end of file From 4a8797b28852918e1ad904f29b9b5bad6c466196 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Vaidyanathan <62492557+VigneshVSV@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:31:42 +0200 Subject: [PATCH 6/6] disable pipeline temporarily --- .github/workflows/ci.yaml | 4 ++-- .gitmodules | 6 ------ deployment/helm/cert-issuer-helm-chart | 1 - deployment/helm/ingress-helm-chart | 1 - pyproject.toml | 2 +- 5 files changed, 3 insertions(+), 11 deletions(-) delete mode 160000 deployment/helm/cert-issuer-helm-chart delete mode 160000 deployment/helm/ingress-helm-chart diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 97d5b65..49695d0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,8 +2,8 @@ name: deploy-docs on: workflow_dispatch: - repository_dispatch: - types: [trigger-downstream] + # repository_dispatch: + # types: [trigger-downstream] env: REGISTRY: ghcr.io diff --git a/.gitmodules b/.gitmodules index f128ae3..c3ca054 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ -[submodule "deployment/helm/cert-issuer-helm-chart"] - path = deployment/helm/cert-issuer-helm-chart - url = https://gitlab.com/hololinked/kubernetes/helm-charts/cert-issuer-helm-chart.git [submodule "deployment/helm/container-helm-chart"] path = deployment/helm/container-helm-chart url = https://gitlab.com/hololinked/kubernetes/helm-charts/container-helm-chart.git -[submodule "deployment/helm/ingress-helm-chart"] - path = deployment/helm/ingress-helm-chart - url = https://gitlab.com/hololinked/kubernetes/helm-charts/ingress-helm-chart.git diff --git a/deployment/helm/cert-issuer-helm-chart b/deployment/helm/cert-issuer-helm-chart deleted file mode 160000 index fbcbd12..0000000 --- a/deployment/helm/cert-issuer-helm-chart +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fbcbd12dcc5de9943de1a245265842cadec224dc diff --git a/deployment/helm/ingress-helm-chart b/deployment/helm/ingress-helm-chart deleted file mode 160000 index 343aff1..0000000 --- a/deployment/helm/ingress-helm-chart +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 343aff1c89af401fdc1dad5237153e7070a96793 diff --git a/pyproject.toml b/pyproject.toml index 3f90891..d2a0198 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,4 +36,4 @@ repo = [ ] [tool.uv.sources] -hololinked = { git = "https://github.com/hololinked-dev/hololinked.git", branch = "mypy" } \ No newline at end of file +hololinked = { git = "https://github.com/hololinked-dev/hololinked.git", branch = "main" } \ No newline at end of file