DWIS Python client
Python 3.10+ implementation of the supported C# Blackboard client API, using asyncua 2.1.x. DTOs and OPC UA identifiers follow C# client 2.2.52 and DWIS.API.DTO / DWIS.OPCUA.Schemas 2.2.33. Install the distribution named asyncua; opcua-asyncio is the upstream repository name.
Installation and verification
From this directory:
python -m pip install ".[test]" build
python -m pytest
python -m build
Offline tests include synthetic fixtures exported by the real C# DTO serializer. The workflow .github/workflows/python-publish.yml tests Python 3.10, 3.12 and 3.14, validates wheel/source-distribution metadata and retains reports/packages for 14 days. Main-branch pushes, pull requests and manual dispatch only verify/build. Pushing a python-v<version> tag publishes the tested Python 3.12 distributions to PyPI after every matrix job passes. The tag must match the package version in pyproject.toml. Publication uses PyPI Trusted Publishing for D-WIS/DWIS-Blackboard-Clients, workflow python-publish.yml, GitHub environment pypi; no API key is stored. For example, release version 0.1.0 with git tag python-v0.1.0 then git push origin python-v0.1.0. Each subsequent release needs a new package version and matching tag.
Opt in to local Blackboard tests:
python -m pytest --blackboard-url opc.tcp://localhost:48030
python -m pytest --blackboard-url opc.tcp://localhost:48030 --blackboard-writes
The second command also creates uniquely named PythonClientInterop<UUID> manifests and writes only their variables. These test manifests and server-side registrations remain on the Blackboard because the C# API does not implement deletion/unregistration. Use a disposable Blackboard for repeated write testing. Client sessions and monitored items are closed after each test. No server is contacted by the default test command.
Async client
import asyncio
from dwis_client import DwisClientConfiguration, OpcUaDwisClient
async def main():
config = DwisClientConfiguration(server_address="opc.tcp://localhost:48030")
async with OpcUaDwisClient(config) as client:
query = await client.get_query_result("SELECT ?s WHERE { ?s ?p ?o } LIMIT 1")
for row in query.results:
print([(item.namespace, item.identifier) if item else None for item in row.items])
asyncio.run(main())
Construction is lazy; async with or await client.connect() establishes a connection. Always call close() or use the context manager. BLACKBOARD_URL overrides the configured address. Configuration accepts both Python field names and the C# ServerAddress, UseWebAPI, and WebAPIUrl spelling; it saves using C# spelling. The Web API fields are preserved without claiming an HTTP implementation.
The default transport uses an anonymous OPC UA session with SecurityPolicy.None. To configure credentials, certificates or another security policy, supply an adapter factory returning UaClientAdapter(endpoint, client_factory=...). The inner factory returns your configured asyncua.Client. Default requests wait at most 15 seconds; the default client advertises a 16 MiB transport-message ceiling. Custom factories control their own transport settings.
Synchronous client
from dwis_client import SyncOpcUaDwisClient
with SyncOpcUaDwisClient() as client:
result = client.get_query_result("SELECT ?s WHERE { ?s ?p ?o } LIMIT 1")
print(result.variables_header)
The facade owns one persistent event loop on a background thread, preserving the session across calls. It replaces the scaffold's asyncio.run()-per-operation helper. Do not call synchronous facade methods from its notification callback thread; use the async API there. Closing the facade also stops its loop thread.
C# API correspondence
| C# | Python |
|---|---|
| Connected / TryReconnect / ConfigurationUpdated | is_connected / try_reconnect() / configuration_updated() |
| Inject(ManifestFile) | inject() or inject_manifest() with a typed manifest or JSON |
| Resolve(AcquisitionFile) | resolve() or resolve_acquisition_file() |
| GetQueryResult / GetResources | get_query_result() / get_resources() |
| RegisterQuery | register_query(), returning initial diff and registration node |
| RegisterAcquisitionFile | register_acquisition_file() with a typed file or JSON and callback |
| UpdateProvidedVariables | update_provided_variables() using injected manifest-item IDs |
| ReadAnyVariables / UpdateAnyVariables / Subscribe with namespace index | read_any_variables() / update_any_variables() / subscribe() using DwisNodeId |
| Same methods with namespace URI | read_any_variables_by_namespace() / update_any_variables_by_namespace() / subscribe_by_namespace() |
| GetNameSpaceIndex / GetNameSpace | get_namespace_index() / get_namespace_uri() (also get_namespace()) |
| AcquiredSignal / AcquiredSignals | AcquiredSignal / AcquiredSignals.create_with_subscription() |
| Manifest Update / Delete | Explicit NotImplementedError, matching the unimplemented C# transports |
read_any_variables(nodes) returns a list or None on failure. To use the C# out-parameter pattern, pass an output list: read_any_variables(nodes, out_values) returns bool and preserves the list on failure. Empty input returns None/false. Namespace methods return an index/URI or None; namespace indices are unsigned 0–65535. DwisNodeId.parse() preserves numeric, string, GUID and opaque identifier kinds, including strings containing semicolons or equals signs.
Typed contracts and subscriptions
Manifest inputs, injection mappings, query rows/diffs, resources and nested acquisition results/diffs are dataclasses. Use from_json() / to_json() or from_dict() / to_dict(). C# wire names, including ID/URI acronyms and ITemResults, are preserved. AcquisitionFile serializes as a bare array. Query row cells can be None for unbound SPARQL variables. The unfinished port's generic DTO fields have been replaced; ProvidedVariable now describes manifest input, while InjectionMapping describes injected IDs.
from dwis_client import SubscriptionRequest
await client.subscribe("group context", on_changes, [SubscriptionRequest(node_id, "per-node context")])
on_changes(context, changes) receives values, source/server timestamps and per-node user data. It can be synchronous or async. Callbacks execute in order on the client event loop; exceptions are logged without stopping subsequent callbacks. Keep synchronous callbacks short. Data subscriptions report an initial read as well as later notifications.
Query registration returns its initial diff separately and monitors the diff's QueryResultID variable rather than its registration object. Null initial variable values are ignored. Acquisition subscriptions monitor Server events filtered by SourceNode and decode the Message field, wrapping a single AcquisitionFileDiff into an AcquisitionDiff.
try_reconnect() restores data/query/acquisition monitored items against the same server. It assumes the previously returned registrations still exist; register again after a server restart that drops them. Changing the configured endpoint discards cached mappings and subscription bindings to prevent cross-server ID reuse. Inject and register again on the new Blackboard.
The default transport uses sixty-second request and health-check deadlines and subscription keepalives, with five seconds of extra allowance for the outer operation deadline. Set UaClientAdapter(..., request_timeout=...) to adjust request and health-check deadlines; a custom client factory can configure OPC UA security. Semantic import processing on a busy Blackboard can delay query and acquisition notifications beyond ordinary value writes.
Writes inspect every OPC UA status and return false for rejected writes. Unknown provided-variable IDs prevent the entire request from being sent. Multi-node writes are not transactional: some nodes can change even if another write is rejected. Source timestamps must be timezone-aware datetimes; no server timestamp is fabricated. ua.Variant values may be supplied when a particular OPC UA numeric width is needed.
High-level service/transport failures raise DwisClientError; malformed DTO payloads raise ValueError. Cancellation is propagated, and failed monitored-item creation is cleaned up.
Acquired signals
from dwis_client import AcquiredSignals
signals = await AcquiredSignals.create_with_subscription(acquisition_file, client)
for signal in signals["pressure"]:
signal.callback = lambda value, timestamp: print(value, timestamp)
The helper selects the lowest-index criterion, deduplicates signal IDs and subscribes to newly discovered signals. Call update_from_acquisition(file, client, convert_to_si=True) to add new results. The default keeps canonical SI values. With convert_to_si=False, it looks up unit factors and converts displayed floating-point values as (value - A) / B. Conversion never writes back to the server. Source time takes precedence over server time. History is bounded by the constructor's max_log_capacity; zero disables logging. get_logged_data(start) returns a snapshot of retained samples at or after the requested time.
Metadata
Release files for dwis-client 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dwis_client-0.1.0.tar.gz | 27.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dwis_client-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.5 kB
Release files / dwis_client-0.1.0.tar.gz
| Download URL | dwis_client-0.1.0.tar.gz |
|---|---|
| Size | 27.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
86cec8ea3d93ad7d5a3d28bf9b1b49514146515ffb7c824ea30d7f0cbda3bcf9
|
|
BLAKE2b-256 checksum How to use checksums |
68fe33c7f0c7bd94e6b9a68d229239a7c6668d0826baecd59afa4928cf707cd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / dwis_client-0.1.0-py3-none-any.whl
| Download URL | dwis_client-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b262827b6d9f0b75dc1652c7eb07085c2eba8b98ad74ca9279bade3a6f0395c1
|
|
BLAKE2b-256 checksum How to use checksums |
533ee9540d5829abaac4658c595d1cddcc4c63bee1ba41965a035836fed36437
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for dwis-client 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for dwis_client-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.