rename: apply ZB.MOM.WW prefix to all client SDKs + fix pre-existing alarm-RPC breaks

Rename across every client surface using each language's idiomatic convention:

  * .NET   clients/dotnet/MxGateway.Client[.Cli|.Tests]/
             -> clients/dotnet/ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]/
             namespaces -> ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]
             contracts ProjectReference repointed to ZB.MOM.WW.MxGateway.Contracts
             sln migrated to slnx (dotnet sln migrate)
  * Python src/mxgateway -> src/zb_mom_ww_mxgateway
             src/mxgateway_cli -> src/zb_mom_ww_mxgateway_cli
             distribution: mxaccess-gateway-client -> zb-mom-ww-mxaccess-gateway-client
  * Rust   crate: mxgateway-client -> zb-mom-ww-mxgateway-client
             build.rs proto path repointed
  * Java   subprojects: mxgateway-{client,cli} -> zb-mom-ww-mxgateway-{client,cli}
             packages com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
             group   com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
             rootProject mxaccessgw-java -> zb-mom-ww-mxaccessgw-java
  * Go     generate-proto.ps1 proto path repointed; module path and
             package mxgateway kept (Go convention).
  * proto-inputs.json: generatedOutputs.python updated to new package path.
  * scripts/run-client-e2e-tests.ps1: Java CLI install path + gradle task
             updated to zb-mom-ww-mxgateway-cli.

CLI binary names (mxgw, mxgw-py, mxgw-go, mxgateway-cli) and wire-level
identifiers (MXGATEWAY_* env vars, the mxgw_<id>_<secret> API key
prefix, protobuf package names like mxaccess_gateway.v1, all MXAccess
references) intentionally NOT renamed.

Fix pre-existing alarms-over-gateway breaks unblocked by the rename:

  * mxaccess_gateway.proto: add missing public message QueryActiveAlarmsRequest
    {session_id, client_correlation_id, alarm_filter_prefix} and missing
    rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns
    (stream ActiveAlarmSnapshot). All four typed clients referenced
    these but they were absent from the proto.
  * MxAccessGatewayService.QueryActiveAlarms: implement the new RPC on
    the server, streaming from IGatewayAlarmService.CurrentAlarms with
    optional alarm_filter_prefix filter.
  * clients/dotnet/.../DiscoverHierarchyOptions.cs: add the hand-written
    .NET POCO that wraps DiscoverHierarchyRequest (referenced by
    GalaxyRepositoryClient.DiscoverHierarchyAsync but never authored).
  * Drop retired session_id field references from
    AcknowledgeAlarmRequest/AcknowledgeAlarmReply test fixtures across
    .NET, Rust, Go, and Python clients.
  * Rust integration test: add the missing stream_alarms impl on the
    fake MxAccessGateway server (the trait gained the method, fake
    didn't).
  * Rust CLI test: bump expected gatewayProtocolVersion 2 -> 3.

Regenerated artifacts updated in this commit:
  * src/ZB.MOM.WW.MxGateway.Contracts/Generated/{MxaccessGateway,MxaccessGatewayGrpc}.cs
  * clients/python/src/zb_mom_ww_mxgateway/generated/*_pb2{,_grpc}.py
  * clients/go/internal/generated/*.pb.go
(C# regenerated by Grpc.Tools on contracts build; Python and Go via
their generate-proto.ps1 scripts; Rust regenerates from .proto via
tonic-build at compile time so no checked-in artefact.)

Verification: 472 server tests, 275 worker tests (9 dev-rig skipped),
18 integration tests (live MxAccess + LDAP + Galaxy), 57 .NET client
tests, 32 Rust workspace tests, 39 Python tests, all Go packages, and
gradle build for Java all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-23 19:09:34 -04:00
parent dc9c0c950c
commit 397d3c5c4f
142 changed files with 38852 additions and 2137 deletions
@@ -0,0 +1,50 @@
"""MXAccess Gateway Python client package."""
from .auth import ApiKey, auth_metadata
from .client import GatewayClient
from .galaxy import GalaxyRepositoryClient
from .generated.galaxy_repository_pb2 import (
DeployEvent,
GalaxyAttribute,
GalaxyObject,
WatchDeployEventsRequest,
)
from .errors import (
MxAccessError,
MxGatewayAuthenticationError,
MxGatewayAuthorizationError,
MxGatewayCommandError,
MxGatewayError,
MxGatewaySessionError,
MxGatewayTransportError,
MxGatewayWorkerError,
)
from .options import ClientOptions
from .session import Session
from .values import MxValueView, from_mx_value, to_mx_value
from .version import __version__
__all__ = [
"ApiKey",
"ClientOptions",
"DeployEvent",
"GalaxyAttribute",
"GalaxyObject",
"GalaxyRepositoryClient",
"GatewayClient",
"MxAccessError",
"MxGatewayAuthenticationError",
"MxGatewayAuthorizationError",
"MxGatewayCommandError",
"MxGatewayError",
"MxGatewaySessionError",
"MxGatewayTransportError",
"MxGatewayWorkerError",
"MxValueView",
"Session",
"WatchDeployEventsRequest",
"__version__",
"auth_metadata",
"from_mx_value",
"to_mx_value",
]
@@ -0,0 +1,61 @@
"""Authentication metadata helpers for MXAccess Gateway clients."""
from collections.abc import Sequence
from dataclasses import dataclass
AUTHORIZATION_HEADER = "authorization"
REDACTED = "[redacted]"
@dataclass(frozen=True)
class ApiKey:
"""API key wrapper that avoids leaking the secret through repr output."""
value: str
def __post_init__(self) -> None:
"""Validate that the API key value is non-empty."""
if not self.value:
raise ValueError("api_key must not be empty")
def __repr__(self) -> str:
"""Return a repr that redacts the secret value."""
return f"{type(self).__name__}({REDACTED!r})"
def bearer_value(self) -> str:
"""Return the value formatted as an HTTP `Bearer` token."""
return f"Bearer {self.value}"
def auth_metadata(api_key: str | ApiKey | None) -> tuple[tuple[str, str], ...]:
"""Return gRPC metadata for API key auth."""
if api_key is None:
return ()
key = api_key.value if isinstance(api_key, ApiKey) else api_key
if not key:
return ()
return ((AUTHORIZATION_HEADER, f"Bearer {key}"),)
def merge_metadata(
api_key: str | ApiKey | None,
metadata: Sequence[tuple[str, str]] | None = None,
) -> tuple[tuple[str, str], ...]:
"""Merge caller metadata with API key metadata."""
merged = list(metadata or ())
merged.extend(auth_metadata(api_key))
return tuple(merged)
def redact_secret(text: str, secrets: Sequence[str | None]) -> str:
"""Replace known secret values with a stable redaction marker."""
redacted = text
for secret in secrets:
if secret:
redacted = redacted.replace(secret, REDACTED)
return redacted
@@ -0,0 +1,225 @@
"""Async MXAccess Gateway client wrapper."""
from __future__ import annotations
import asyncio
from collections.abc import AsyncIterator, Sequence
from typing import Any
import grpc
from .auth import merge_metadata
from .errors import ensure_protocol_success, map_rpc_error
from .generated import mxaccess_gateway_pb2 as pb
from .generated import mxaccess_gateway_pb2_grpc as pb_grpc
from .options import ClientOptions, create_channel
class GatewayClient:
"""Async client for the public MXAccess Gateway gRPC API."""
def __init__(
self,
*,
options: ClientOptions,
stub: Any,
channel: grpc.aio.Channel | None = None,
) -> None:
"""Initialize the client with resolved options and a gRPC stub."""
self.options = options
self.raw_stub = stub
self._channel = channel
self._closed = False
@classmethod
async def connect(
cls,
options: ClientOptions | None = None,
*,
endpoint: str | None = None,
api_key: str | None = None,
plaintext: bool = False,
ca_file: str | None = None,
server_name_override: str | None = None,
stub: Any | None = None,
) -> "GatewayClient":
"""Create a client with either a real async channel or a supplied fake stub."""
resolved = options or ClientOptions(
endpoint=endpoint or "",
api_key=api_key,
plaintext=plaintext,
ca_file=ca_file,
server_name_override=server_name_override,
)
if stub is not None:
return cls(options=resolved, stub=stub)
channel = create_channel(resolved)
return cls(
options=resolved,
stub=pb_grpc.MxAccessGatewayStub(channel),
channel=channel,
)
async def __aenter__(self) -> "GatewayClient":
"""Return self to support ``async with`` usage."""
return self
async def __aexit__(self, *_exc_info: object) -> None:
"""Close the client when leaving an ``async with`` block."""
await self.close()
async def close(self) -> None:
"""Close the owned gRPC channel."""
if self._closed:
return
if self._channel is not None:
await self._channel.close()
self._closed = True
async def open_session(
self,
request: pb.OpenSessionRequest | None = None,
*,
requested_backend: str = "",
client_session_name: str = "",
client_correlation_id: str = "",
) -> "Session":
"""Open a gateway session and return a high-level session wrapper."""
from .session import Session
raw_request = request or pb.OpenSessionRequest(
requested_backend=requested_backend,
client_session_name=client_session_name,
client_correlation_id=client_correlation_id,
)
reply = await self.open_session_raw(raw_request)
return Session(client=self, session_id=reply.session_id, open_reply=reply)
async def open_session_raw(self, request: pb.OpenSessionRequest) -> pb.OpenSessionReply:
"""Send an `OpenSession` RPC and return the raw reply."""
reply = await self._unary("open session", self.raw_stub.OpenSession, request)
ensure_protocol_success("open session", reply.protocol_status, reply)
return reply
async def close_session_raw(
self,
request: pb.CloseSessionRequest,
) -> pb.CloseSessionReply:
"""Send a `CloseSession` RPC and return the raw reply."""
reply = await self._unary("close session", self.raw_stub.CloseSession, request)
ensure_protocol_success("close session", reply.protocol_status, reply)
return reply
async def invoke_raw(self, request: pb.MxCommandRequest) -> pb.MxCommandReply:
"""Send an `Invoke` RPC and return the raw reply."""
reply = await self._unary("invoke", self.raw_stub.Invoke, request)
ensure_protocol_success("invoke", reply.protocol_status, reply)
return reply
def stream_events_raw(
self,
request: pb.StreamEventsRequest,
*,
metadata: Sequence[tuple[str, str]] | None = None,
) -> AsyncIterator[pb.MxEvent]:
"""Return an async event iterator and cancel the stream when iteration stops."""
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.stream_timeout is not None:
kwargs["timeout"] = self.options.stream_timeout
call = self.raw_stub.StreamEvents(request, **kwargs)
return _canceling_iterator(call)
async def acknowledge_alarm(
self,
request: pb.AcknowledgeAlarmRequest,
) -> pb.AcknowledgeAlarmReply:
"""Acknowledge an active MXAccess alarm condition through the gateway.
The gateway authenticates the request against the API key's
``invoke:alarm-ack`` scope and forwards the acknowledge to the worker's
MXAccess session; the resulting native ``MxStatus`` is returned in the
reply. Acks are idempotent — re-acking an already-acked condition is a
no-op at the MxAccess layer.
"""
reply = await self._unary("acknowledge alarm", self.raw_stub.AcknowledgeAlarm, request)
ensure_protocol_success("acknowledge alarm", reply.protocol_status, reply)
return reply
def query_active_alarms(
self,
request: pb.QueryActiveAlarmsRequest,
*,
metadata: Sequence[tuple[str, str]] | None = None,
) -> AsyncIterator[pb.ActiveAlarmSnapshot]:
"""Stream a snapshot of all alarms currently Active or ActiveAcked.
The gateway's ConditionRefresh equivalent. Use after reconnect to seed
local Part 9 state, or to reconcile alarms that may have been missed
during a transport blip. Optionally scoped by alarm-reference prefix
(``request.alarm_filter_prefix``) so a partial refresh can target an
equipment sub-tree.
"""
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.stream_timeout is not None:
kwargs["timeout"] = self.options.stream_timeout
call = self.raw_stub.QueryActiveAlarms(request, **kwargs)
return _canceling_active_alarms_iterator(call)
async def _unary(
self,
operation: str,
method: Any,
request: Any,
*,
metadata: Sequence[tuple[str, str]] | None = None,
) -> Any:
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.call_timeout is not None:
kwargs["timeout"] = self.options.call_timeout
try:
call = method(request, **kwargs)
except TypeError as error:
if "timeout" not in kwargs or "unexpected keyword argument 'timeout'" not in str(error):
raise
kwargs.pop("timeout")
call = method(request, **kwargs)
try:
return await call
except asyncio.CancelledError:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
raise
except grpc.RpcError as error:
raise map_rpc_error(operation, error) from error
async def _canceling_iterator(call: Any) -> AsyncIterator[pb.MxEvent]:
try:
async for event in call:
yield event
except grpc.RpcError as error:
raise map_rpc_error("stream events", error) from error
finally:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
async def _canceling_active_alarms_iterator(call: Any) -> AsyncIterator[pb.ActiveAlarmSnapshot]:
try:
async for snapshot in call:
yield snapshot
except grpc.RpcError as error:
raise map_rpc_error("query active alarms", error) from error
finally:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
@@ -0,0 +1,158 @@
"""Typed exception model for MXAccess Gateway Python clients."""
from __future__ import annotations
from typing import Any
import grpc
from .generated import mxaccess_gateway_pb2 as pb
class MxGatewayError(Exception):
"""Base class for client wrapper errors."""
def __init__(
self,
message: str,
*,
protocol_status: pb.ProtocolStatus | None = None,
raw_reply: Any | None = None,
) -> None:
"""Initialize with a message and the optional raw protocol context."""
super().__init__(message)
self.protocol_status = protocol_status
self.raw_reply = raw_reply
class MxGatewayTransportError(MxGatewayError):
"""Transport-level gRPC failure."""
class MxGatewayAuthenticationError(MxGatewayTransportError):
"""Authentication failure reported by gRPC."""
class MxGatewayAuthorizationError(MxGatewayTransportError):
"""Authorization failure reported by gRPC."""
class MxGatewaySessionError(MxGatewayError):
"""Gateway session failure."""
class MxGatewayWorkerError(MxGatewayError):
"""Gateway worker process or protocol failure."""
class MxGatewayCommandError(MxGatewayError):
"""Command failure that preserves the raw protobuf reply."""
class MxAccessError(MxGatewayCommandError):
"""MXAccess HRESULT or status failure."""
def map_rpc_error(operation: str, error: grpc.RpcError) -> MxGatewayTransportError:
"""Map a generated gRPC exception to the client exception hierarchy."""
code = error.code() if hasattr(error, "code") else None
details = error.details() if hasattr(error, "details") else str(error)
message = f"{operation} failed: {details}"
if code == grpc.StatusCode.UNAUTHENTICATED:
return MxGatewayAuthenticationError(message)
if code == grpc.StatusCode.PERMISSION_DENIED:
return MxGatewayAuthorizationError(message)
return MxGatewayTransportError(message)
def ensure_protocol_success(
operation: str,
protocol_status: pb.ProtocolStatus | None,
raw_reply: Any | None = None,
) -> Any | None:
"""Raise typed gateway errors for non-OK protocol statuses."""
code = (
protocol_status.code
if protocol_status is not None
else pb.PROTOCOL_STATUS_CODE_UNSPECIFIED
)
if code in (
pb.PROTOCOL_STATUS_CODE_OK,
pb.PROTOCOL_STATUS_CODE_MXACCESS_FAILURE,
):
return raw_reply
message_text = protocol_status.message if protocol_status else ""
message = f"{operation} failed: {message_text or pb.ProtocolStatusCode.Name(code)}"
if code in (
pb.PROTOCOL_STATUS_CODE_SESSION_NOT_FOUND,
pb.PROTOCOL_STATUS_CODE_SESSION_NOT_READY,
):
raise MxGatewaySessionError(
message,
protocol_status=protocol_status,
raw_reply=raw_reply,
)
if code in (
pb.PROTOCOL_STATUS_CODE_WORKER_UNAVAILABLE,
pb.PROTOCOL_STATUS_CODE_TIMEOUT,
pb.PROTOCOL_STATUS_CODE_CANCELED,
pb.PROTOCOL_STATUS_CODE_PROTOCOL_VIOLATION,
):
raise MxGatewayWorkerError(
message,
protocol_status=protocol_status,
raw_reply=raw_reply,
)
raise MxGatewayCommandError(
message,
protocol_status=protocol_status,
raw_reply=raw_reply,
)
def ensure_mxaccess_success(operation: str, reply: pb.MxCommandReply) -> pb.MxCommandReply:
"""Raise `MxAccessError` when MXAccess returned HRESULT or status failure."""
status = reply.protocol_status
if status.code == pb.PROTOCOL_STATUS_CODE_MXACCESS_FAILURE:
raise MxAccessError(
_mxaccess_message(operation, reply),
protocol_status=status,
raw_reply=reply,
)
if reply.HasField("hresult") and reply.hresult < 0:
raise MxAccessError(
_mxaccess_message(operation, reply),
protocol_status=status,
raw_reply=reply,
)
for mx_status in reply.statuses:
if mx_status.success == 0:
raise MxAccessError(
_mxaccess_message(operation, reply),
protocol_status=status,
raw_reply=reply,
)
return reply
def _mxaccess_message(operation: str, reply: pb.MxCommandReply) -> str:
status_text = reply.protocol_status.message or "MXAccess command failed"
hresult = reply.hresult if reply.HasField("hresult") else None
return (
f"{operation} failed: {status_text}; "
f"session={reply.session_id}; correlation={reply.correlation_id}; "
f"hresult={hresult}; statuses={len(reply.statuses)}"
)
@@ -0,0 +1,219 @@
"""Async Galaxy Repository client wrapper.
Wraps the read-only ``GalaxyRepository`` gRPC service exposed by the
MxAccess Gateway. The service lets callers test connectivity to the AVEVA
System Platform Galaxy Repository (ZB SQL database), read the last
deployment timestamp, and enumerate the deployed object hierarchy plus the
attributes on each object.
"""
from __future__ import annotations
import asyncio
from collections.abc import AsyncIterator, Sequence
from datetime import datetime
from typing import Any
import grpc
from google.protobuf.timestamp_pb2 import Timestamp
from .auth import merge_metadata
from .errors import MxGatewayError, map_rpc_error
from .generated import galaxy_repository_pb2 as galaxy_pb
from .generated import galaxy_repository_pb2_grpc as galaxy_pb_grpc
from .options import ClientOptions, create_channel
_DISCOVER_HIERARCHY_PAGE_SIZE = 5000
class GalaxyRepositoryClient:
"""Async client for the Galaxy Repository gRPC service."""
def __init__(
self,
*,
options: ClientOptions,
stub: Any,
channel: grpc.aio.Channel | None = None,
) -> None:
"""Initialize the client with resolved options and a gRPC stub."""
self.options = options
self.raw_stub = stub
self._channel = channel
self._closed = False
@classmethod
async def connect(
cls,
options: ClientOptions | None = None,
*,
endpoint: str | None = None,
api_key: str | None = None,
plaintext: bool = False,
ca_file: str | None = None,
server_name_override: str | None = None,
stub: Any | None = None,
) -> "GalaxyRepositoryClient":
"""Create a client with either a real async channel or a supplied fake stub."""
resolved = options or ClientOptions(
endpoint=endpoint or "",
api_key=api_key,
plaintext=plaintext,
ca_file=ca_file,
server_name_override=server_name_override,
)
if stub is not None:
return cls(options=resolved, stub=stub)
channel = create_channel(resolved)
return cls(
options=resolved,
stub=galaxy_pb_grpc.GalaxyRepositoryStub(channel),
channel=channel,
)
async def __aenter__(self) -> "GalaxyRepositoryClient":
"""Return self to support ``async with`` usage."""
return self
async def __aexit__(self, *_exc_info: object) -> None:
"""Close the client when leaving an ``async with`` block."""
await self.close()
async def close(self) -> None:
"""Close the owned gRPC channel."""
if self._closed:
return
if self._channel is not None:
await self._channel.close()
self._closed = True
async def test_connection(self) -> bool:
"""Return ``True`` when the gateway can reach the Galaxy Repository DB."""
reply = await self._unary(
"test connection",
self.raw_stub.TestConnection,
galaxy_pb.TestConnectionRequest(),
)
return bool(reply.ok)
async def get_last_deploy_time(self) -> datetime | None:
"""Return the last Galaxy deploy timestamp or ``None`` when unset."""
reply = await self._unary(
"get last deploy time",
self.raw_stub.GetLastDeployTime,
galaxy_pb.GetLastDeployTimeRequest(),
)
if not reply.present:
return None
return reply.time_of_last_deploy.ToDatetime()
async def discover_hierarchy(self) -> list[galaxy_pb.GalaxyObject]:
"""Return the deployed Galaxy object hierarchy as raw proto messages."""
objects: list[galaxy_pb.GalaxyObject] = []
seen_page_tokens: set[str] = set()
page_token = ""
while True:
reply = await self._unary(
"discover hierarchy",
self.raw_stub.DiscoverHierarchy,
galaxy_pb.DiscoverHierarchyRequest(
page_size=_DISCOVER_HIERARCHY_PAGE_SIZE,
page_token=page_token,
),
)
objects.extend(reply.objects)
page_token = reply.next_page_token
if not page_token:
return objects
if page_token in seen_page_tokens:
raise MxGatewayError(
f"galaxy discover hierarchy returned repeated page token {page_token!r}"
)
seen_page_tokens.add(page_token)
def watch_deploy_events(
self,
last_seen_deploy_time: datetime | None = None,
*,
metadata: Sequence[tuple[str, str]] | None = None,
) -> AsyncIterator[galaxy_pb.DeployEvent]:
"""Stream Galaxy deploy events.
On subscribe the gateway emits the current cached state and then one
event per new deploy time. ``sequence`` is monotonic per server start;
gaps mean events were dropped from the per-subscriber buffer. When
``last_seen_deploy_time`` is supplied and matches the current cached
deploy time the bootstrap event is suppressed.
"""
request = galaxy_pb.WatchDeployEventsRequest()
if last_seen_deploy_time is not None:
timestamp = Timestamp()
timestamp.FromDatetime(last_seen_deploy_time)
request.last_seen_deploy_time.CopyFrom(timestamp)
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.stream_timeout is not None:
kwargs["timeout"] = self.options.stream_timeout
try:
call = self.raw_stub.WatchDeployEvents(request, **kwargs)
except TypeError as error:
if "timeout" not in kwargs or "unexpected keyword argument 'timeout'" not in str(error):
raise
kwargs.pop("timeout")
call = self.raw_stub.WatchDeployEvents(request, **kwargs)
return _canceling_iterator(call)
async def _unary(
self,
operation: str,
method: Any,
request: Any,
*,
metadata: Sequence[tuple[str, str]] | None = None,
) -> Any:
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.call_timeout is not None:
kwargs["timeout"] = self.options.call_timeout
try:
call = method(request, **kwargs)
except TypeError as error:
if "timeout" not in kwargs or "unexpected keyword argument 'timeout'" not in str(error):
raise
kwargs.pop("timeout")
call = method(request, **kwargs)
try:
return await call
except asyncio.CancelledError:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
raise
except grpc.RpcError as error:
raise map_rpc_error(operation, error) from error
async def _canceling_iterator(call: Any) -> AsyncIterator[galaxy_pb.DeployEvent]:
try:
async for event in call:
yield event
except asyncio.CancelledError:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
raise
except grpc.RpcError as error:
raise map_rpc_error("watch deploy events", error) from error
finally:
cancel = getattr(call, "cancel", None)
if cancel is not None:
cancel()
@@ -0,0 +1 @@
@@ -0,0 +1,37 @@
"""Generated protobuf and gRPC modules for MXAccess Gateway.
The Python protobuf generator emits absolute imports between generated modules.
This package initializer registers package-local aliases so callers can import
the generated stubs through `zb_mom_ww_mxgateway.generated` without moving the modules to
the top-level import namespace.
"""
from importlib import import_module
import sys
mxaccess_gateway_pb2 = import_module(f"{__name__}.mxaccess_gateway_pb2")
sys.modules.setdefault("mxaccess_gateway_pb2", mxaccess_gateway_pb2)
mxaccess_gateway_pb2_grpc = import_module(f"{__name__}.mxaccess_gateway_pb2_grpc")
sys.modules.setdefault("mxaccess_gateway_pb2_grpc", mxaccess_gateway_pb2_grpc)
mxaccess_worker_pb2 = import_module(f"{__name__}.mxaccess_worker_pb2")
sys.modules.setdefault("mxaccess_worker_pb2", mxaccess_worker_pb2)
mxaccess_worker_pb2_grpc = import_module(f"{__name__}.mxaccess_worker_pb2_grpc")
sys.modules.setdefault("mxaccess_worker_pb2_grpc", mxaccess_worker_pb2_grpc)
galaxy_repository_pb2 = import_module(f"{__name__}.galaxy_repository_pb2")
sys.modules.setdefault("galaxy_repository_pb2", galaxy_repository_pb2)
galaxy_repository_pb2_grpc = import_module(f"{__name__}.galaxy_repository_pb2_grpc")
sys.modules.setdefault("galaxy_repository_pb2_grpc", galaxy_repository_pb2_grpc)
__all__ = [
"galaxy_repository_pb2",
"galaxy_repository_pb2_grpc",
"mxaccess_gateway_pb2",
"mxaccess_gateway_pb2_grpc",
"mxaccess_worker_pb2",
"mxaccess_worker_pb2_grpc",
]
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: galaxy_repository.proto
# Protobuf Python Version: 6.31.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
6,
31,
1,
'',
'galaxy_repository.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17galaxy_repository.proto\x12\x14galaxy_repository.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\x17\n\x15TestConnectionRequest\"!\n\x13TestConnectionReply\x12\n\n\x02ok\x18\x01 \x01(\x08\"\x1a\n\x18GetLastDeployTimeRequest\"b\n\x16GetLastDeployTimeReply\x12\x0f\n\x07present\x18\x01 \x01(\x08\x12\x37\n\x13time_of_last_deploy\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x87\x03\n\x18\x44iscoverHierarchyRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\x12\x19\n\x0froot_gobject_id\x18\x03 \x01(\x05H\x00\x12\x17\n\rroot_tag_name\x18\x04 \x01(\tH\x00\x12\x1d\n\x13root_contained_path\x18\x05 \x01(\tH\x00\x12.\n\tmax_depth\x18\x06 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x14\n\x0c\x63\x61tegory_ids\x18\x07 \x03(\x05\x12\x1f\n\x17template_chain_contains\x18\x08 \x03(\t\x12\x15\n\rtag_name_glob\x18\t \x01(\t\x12\x1f\n\x12include_attributes\x18\n \x01(\x08H\x01\x88\x01\x01\x12\x1a\n\x12\x61larm_bearing_only\x18\x0b \x01(\x08\x12\x17\n\x0fhistorized_only\x18\x0c \x01(\x08\x42\x06\n\x04rootB\x15\n\x13_include_attributes\"\x82\x01\n\x16\x44iscoverHierarchyReply\x12\x33\n\x07objects\x18\x01 \x03(\x0b\x32\".galaxy_repository.v1.GalaxyObject\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\x12\x1a\n\x12total_object_count\x18\x03 \x01(\x05\"U\n\x18WatchDeployEventsRequest\x12\x39\n\x15last_seen_deploy_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xdd\x01\n\x0b\x44\x65ployEvent\x12\x10\n\x08sequence\x18\x01 \x01(\x04\x12/\n\x0bobserved_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x37\n\x13time_of_last_deploy\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x1btime_of_last_deploy_present\x18\x04 \x01(\x08\x12\x14\n\x0cobject_count\x18\x05 \x01(\x05\x12\x17\n\x0f\x61ttribute_count\x18\x06 \x01(\x05\"\x93\x02\n\x0cGalaxyObject\x12\x12\n\ngobject_id\x18\x01 \x01(\x05\x12\x10\n\x08tag_name\x18\x02 \x01(\t\x12\x16\n\x0e\x63ontained_name\x18\x03 \x01(\t\x12\x13\n\x0b\x62rowse_name\x18\x04 \x01(\t\x12\x19\n\x11parent_gobject_id\x18\x05 \x01(\x05\x12\x0f\n\x07is_area\x18\x06 \x01(\x08\x12\x13\n\x0b\x63\x61tegory_id\x18\x07 \x01(\x05\x12\x1c\n\x14hosted_by_gobject_id\x18\x08 \x01(\x05\x12\x16\n\x0etemplate_chain\x18\t \x03(\t\x12\x39\n\nattributes\x18\n \x03(\x0b\x32%.galaxy_repository.v1.GalaxyAttribute\"\xa8\x02\n\x0fGalaxyAttribute\x12\x16\n\x0e\x61ttribute_name\x18\x01 \x01(\t\x12\x1a\n\x12\x66ull_tag_reference\x18\x02 \x01(\t\x12\x14\n\x0cmx_data_type\x18\x03 \x01(\x05\x12\x16\n\x0e\x64\x61ta_type_name\x18\x04 \x01(\t\x12\x10\n\x08is_array\x18\x05 \x01(\x08\x12\x17\n\x0f\x61rray_dimension\x18\x06 \x01(\x05\x12\x1f\n\x17\x61rray_dimension_present\x18\x07 \x01(\x08\x12\x1d\n\x15mx_attribute_category\x18\x08 \x01(\x05\x12\x1f\n\x17security_classification\x18\t \x01(\x05\x12\x15\n\ris_historized\x18\n \x01(\x08\x12\x10\n\x08is_alarm\x18\x0b \x01(\x08\x32\xcc\x03\n\x10GalaxyRepository\x12h\n\x0eTestConnection\x12+.galaxy_repository.v1.TestConnectionRequest\x1a).galaxy_repository.v1.TestConnectionReply\x12q\n\x11GetLastDeployTime\x12..galaxy_repository.v1.GetLastDeployTimeRequest\x1a,.galaxy_repository.v1.GetLastDeployTimeReply\x12q\n\x11\x44iscoverHierarchy\x12..galaxy_repository.v1.DiscoverHierarchyRequest\x1a,.galaxy_repository.v1.DiscoverHierarchyReply\x12h\n\x11WatchDeployEvents\x12..galaxy_repository.v1.WatchDeployEventsRequest\x1a!.galaxy_repository.v1.DeployEvent0\x01\x42-\xaa\x02*ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxyb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'galaxy_repository_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options = b'\252\002*ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy'
_globals['_TESTCONNECTIONREQUEST']._serialized_start=114
_globals['_TESTCONNECTIONREQUEST']._serialized_end=137
_globals['_TESTCONNECTIONREPLY']._serialized_start=139
_globals['_TESTCONNECTIONREPLY']._serialized_end=172
_globals['_GETLASTDEPLOYTIMEREQUEST']._serialized_start=174
_globals['_GETLASTDEPLOYTIMEREQUEST']._serialized_end=200
_globals['_GETLASTDEPLOYTIMEREPLY']._serialized_start=202
_globals['_GETLASTDEPLOYTIMEREPLY']._serialized_end=300
_globals['_DISCOVERHIERARCHYREQUEST']._serialized_start=303
_globals['_DISCOVERHIERARCHYREQUEST']._serialized_end=694
_globals['_DISCOVERHIERARCHYREPLY']._serialized_start=697
_globals['_DISCOVERHIERARCHYREPLY']._serialized_end=827
_globals['_WATCHDEPLOYEVENTSREQUEST']._serialized_start=829
_globals['_WATCHDEPLOYEVENTSREQUEST']._serialized_end=914
_globals['_DEPLOYEVENT']._serialized_start=917
_globals['_DEPLOYEVENT']._serialized_end=1138
_globals['_GALAXYOBJECT']._serialized_start=1141
_globals['_GALAXYOBJECT']._serialized_end=1416
_globals['_GALAXYATTRIBUTE']._serialized_start=1419
_globals['_GALAXYATTRIBUTE']._serialized_end=1715
_globals['_GALAXYREPOSITORY']._serialized_start=1718
_globals['_GALAXYREPOSITORY']._serialized_end=2178
# @@protoc_insertion_point(module_scope)
@@ -0,0 +1,265 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import galaxy_repository_pb2 as galaxy__repository__pb2
GRPC_GENERATED_VERSION = '1.80.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in galaxy_repository_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class GalaxyRepositoryStub(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved`
declarations today because no field or enum value has ever been removed.
Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
database). Lets clients enumerate the deployed object hierarchy and each
object's dynamic attributes so they know what tag references to subscribe
to via the MxAccessGateway service.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.TestConnection = channel.unary_unary(
'/galaxy_repository.v1.GalaxyRepository/TestConnection',
request_serializer=galaxy__repository__pb2.TestConnectionRequest.SerializeToString,
response_deserializer=galaxy__repository__pb2.TestConnectionReply.FromString,
_registered_method=True)
self.GetLastDeployTime = channel.unary_unary(
'/galaxy_repository.v1.GalaxyRepository/GetLastDeployTime',
request_serializer=galaxy__repository__pb2.GetLastDeployTimeRequest.SerializeToString,
response_deserializer=galaxy__repository__pb2.GetLastDeployTimeReply.FromString,
_registered_method=True)
self.DiscoverHierarchy = channel.unary_unary(
'/galaxy_repository.v1.GalaxyRepository/DiscoverHierarchy',
request_serializer=galaxy__repository__pb2.DiscoverHierarchyRequest.SerializeToString,
response_deserializer=galaxy__repository__pb2.DiscoverHierarchyReply.FromString,
_registered_method=True)
self.WatchDeployEvents = channel.unary_stream(
'/galaxy_repository.v1.GalaxyRepository/WatchDeployEvents',
request_serializer=galaxy__repository__pb2.WatchDeployEventsRequest.SerializeToString,
response_deserializer=galaxy__repository__pb2.DeployEvent.FromString,
_registered_method=True)
class GalaxyRepositoryServicer(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved`
declarations today because no field or enum value has ever been removed.
Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
database). Lets clients enumerate the deployed object hierarchy and each
object's dynamic attributes so they know what tag references to subscribe
to via the MxAccessGateway service.
"""
def TestConnection(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetLastDeployTime(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def DiscoverHierarchy(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def WatchDeployEvents(self, request, context):
"""Server-stream of deploy events. The server emits the current state immediately
on subscribe (so clients can bootstrap their cache without waiting for the next
deploy), then emits one event each time the gateway's hierarchy cache observes
a new galaxy.time_of_last_deploy. The sequence field is monotonically
increasing per server start; gaps indicate the per-subscriber buffer dropped
older events because the client was too slow.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_GalaxyRepositoryServicer_to_server(servicer, server):
rpc_method_handlers = {
'TestConnection': grpc.unary_unary_rpc_method_handler(
servicer.TestConnection,
request_deserializer=galaxy__repository__pb2.TestConnectionRequest.FromString,
response_serializer=galaxy__repository__pb2.TestConnectionReply.SerializeToString,
),
'GetLastDeployTime': grpc.unary_unary_rpc_method_handler(
servicer.GetLastDeployTime,
request_deserializer=galaxy__repository__pb2.GetLastDeployTimeRequest.FromString,
response_serializer=galaxy__repository__pb2.GetLastDeployTimeReply.SerializeToString,
),
'DiscoverHierarchy': grpc.unary_unary_rpc_method_handler(
servicer.DiscoverHierarchy,
request_deserializer=galaxy__repository__pb2.DiscoverHierarchyRequest.FromString,
response_serializer=galaxy__repository__pb2.DiscoverHierarchyReply.SerializeToString,
),
'WatchDeployEvents': grpc.unary_stream_rpc_method_handler(
servicer.WatchDeployEvents,
request_deserializer=galaxy__repository__pb2.WatchDeployEventsRequest.FromString,
response_serializer=galaxy__repository__pb2.DeployEvent.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'galaxy_repository.v1.GalaxyRepository', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('galaxy_repository.v1.GalaxyRepository', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class GalaxyRepository(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved`
declarations today because no field or enum value has ever been removed.
Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
database). Lets clients enumerate the deployed object hierarchy and each
object's dynamic attributes so they know what tag references to subscribe
to via the MxAccessGateway service.
"""
@staticmethod
def TestConnection(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/galaxy_repository.v1.GalaxyRepository/TestConnection',
galaxy__repository__pb2.TestConnectionRequest.SerializeToString,
galaxy__repository__pb2.TestConnectionReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetLastDeployTime(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/galaxy_repository.v1.GalaxyRepository/GetLastDeployTime',
galaxy__repository__pb2.GetLastDeployTimeRequest.SerializeToString,
galaxy__repository__pb2.GetLastDeployTimeReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def DiscoverHierarchy(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/galaxy_repository.v1.GalaxyRepository/DiscoverHierarchy',
galaxy__repository__pb2.DiscoverHierarchyRequest.SerializeToString,
galaxy__repository__pb2.DiscoverHierarchyReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def WatchDeployEvents(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/galaxy_repository.v1.GalaxyRepository/WatchDeployEvents',
galaxy__repository__pb2.WatchDeployEventsRequest.SerializeToString,
galaxy__repository__pb2.DeployEvent.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,386 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import mxaccess_gateway_pb2 as mxaccess__gateway__pb2
GRPC_GENERATED_VERSION = '1.80.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in mxaccess_gateway_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class MxAccessGatewayStub(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag.
Public client API for MXAccess sessions hosted by the gateway.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.OpenSession = channel.unary_unary(
'/mxaccess_gateway.v1.MxAccessGateway/OpenSession',
request_serializer=mxaccess__gateway__pb2.OpenSessionRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.OpenSessionReply.FromString,
_registered_method=True)
self.CloseSession = channel.unary_unary(
'/mxaccess_gateway.v1.MxAccessGateway/CloseSession',
request_serializer=mxaccess__gateway__pb2.CloseSessionRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.CloseSessionReply.FromString,
_registered_method=True)
self.Invoke = channel.unary_unary(
'/mxaccess_gateway.v1.MxAccessGateway/Invoke',
request_serializer=mxaccess__gateway__pb2.MxCommandRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.MxCommandReply.FromString,
_registered_method=True)
self.StreamEvents = channel.unary_stream(
'/mxaccess_gateway.v1.MxAccessGateway/StreamEvents',
request_serializer=mxaccess__gateway__pb2.StreamEventsRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.MxEvent.FromString,
_registered_method=True)
self.AcknowledgeAlarm = channel.unary_unary(
'/mxaccess_gateway.v1.MxAccessGateway/AcknowledgeAlarm',
request_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.FromString,
_registered_method=True)
self.StreamAlarms = channel.unary_stream(
'/mxaccess_gateway.v1.MxAccessGateway/StreamAlarms',
request_serializer=mxaccess__gateway__pb2.StreamAlarmsRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.AlarmFeedMessage.FromString,
_registered_method=True)
self.QueryActiveAlarms = channel.unary_stream(
'/mxaccess_gateway.v1.MxAccessGateway/QueryActiveAlarms',
request_serializer=mxaccess__gateway__pb2.QueryActiveAlarmsRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.ActiveAlarmSnapshot.FromString,
_registered_method=True)
class MxAccessGatewayServicer(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag.
Public client API for MXAccess sessions hosted by the gateway.
"""
def OpenSession(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def CloseSession(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def Invoke(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def StreamEvents(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def AcknowledgeAlarm(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def StreamAlarms(self, request, context):
"""Session-less central alarm feed. The stream opens with the current
active-alarm snapshot (one `active_alarm` per alarm), then a single
`snapshot_complete`, then a `transition` for every subsequent change.
Served by the gateway's always-on alarm monitor; any number of clients
fan out from the single monitor without opening a worker session.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def QueryActiveAlarms(self, request, context):
"""Point-in-time snapshot of the currently-active alarm set served from the
gateway's always-on alarm monitor cache (session-less). Used after a
reconnect to seed Part 9 client state, or to reconcile alarms that may
have been missed during a transport blip. Streamed so callers can
begin processing without buffering the full set.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_MxAccessGatewayServicer_to_server(servicer, server):
rpc_method_handlers = {
'OpenSession': grpc.unary_unary_rpc_method_handler(
servicer.OpenSession,
request_deserializer=mxaccess__gateway__pb2.OpenSessionRequest.FromString,
response_serializer=mxaccess__gateway__pb2.OpenSessionReply.SerializeToString,
),
'CloseSession': grpc.unary_unary_rpc_method_handler(
servicer.CloseSession,
request_deserializer=mxaccess__gateway__pb2.CloseSessionRequest.FromString,
response_serializer=mxaccess__gateway__pb2.CloseSessionReply.SerializeToString,
),
'Invoke': grpc.unary_unary_rpc_method_handler(
servicer.Invoke,
request_deserializer=mxaccess__gateway__pb2.MxCommandRequest.FromString,
response_serializer=mxaccess__gateway__pb2.MxCommandReply.SerializeToString,
),
'StreamEvents': grpc.unary_stream_rpc_method_handler(
servicer.StreamEvents,
request_deserializer=mxaccess__gateway__pb2.StreamEventsRequest.FromString,
response_serializer=mxaccess__gateway__pb2.MxEvent.SerializeToString,
),
'AcknowledgeAlarm': grpc.unary_unary_rpc_method_handler(
servicer.AcknowledgeAlarm,
request_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.FromString,
response_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.SerializeToString,
),
'StreamAlarms': grpc.unary_stream_rpc_method_handler(
servicer.StreamAlarms,
request_deserializer=mxaccess__gateway__pb2.StreamAlarmsRequest.FromString,
response_serializer=mxaccess__gateway__pb2.AlarmFeedMessage.SerializeToString,
),
'QueryActiveAlarms': grpc.unary_stream_rpc_method_handler(
servicer.QueryActiveAlarms,
request_deserializer=mxaccess__gateway__pb2.QueryActiveAlarmsRequest.FromString,
response_serializer=mxaccess__gateway__pb2.ActiveAlarmSnapshot.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'mxaccess_gateway.v1.MxAccessGateway', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('mxaccess_gateway.v1.MxAccessGateway', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class MxAccessGateway(object):
"""Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag.
Public client API for MXAccess sessions hosted by the gateway.
"""
@staticmethod
def OpenSession(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/OpenSession',
mxaccess__gateway__pb2.OpenSessionRequest.SerializeToString,
mxaccess__gateway__pb2.OpenSessionReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def CloseSession(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/CloseSession',
mxaccess__gateway__pb2.CloseSessionRequest.SerializeToString,
mxaccess__gateway__pb2.CloseSessionReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def Invoke(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/Invoke',
mxaccess__gateway__pb2.MxCommandRequest.SerializeToString,
mxaccess__gateway__pb2.MxCommandReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def StreamEvents(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/StreamEvents',
mxaccess__gateway__pb2.StreamEventsRequest.SerializeToString,
mxaccess__gateway__pb2.MxEvent.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def AcknowledgeAlarm(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/AcknowledgeAlarm',
mxaccess__gateway__pb2.AcknowledgeAlarmRequest.SerializeToString,
mxaccess__gateway__pb2.AcknowledgeAlarmReply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def StreamAlarms(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/StreamAlarms',
mxaccess__gateway__pb2.StreamAlarmsRequest.SerializeToString,
mxaccess__gateway__pb2.AlarmFeedMessage.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def QueryActiveAlarms(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/mxaccess_gateway.v1.MxAccessGateway/QueryActiveAlarms',
mxaccess__gateway__pb2.QueryActiveAlarmsRequest.SerializeToString,
mxaccess__gateway__pb2.ActiveAlarmSnapshot.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: mxaccess_worker.proto
# Protobuf Python Version: 6.31.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
6,
31,
1,
'',
'mxaccess_worker.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
import mxaccess_gateway_pb2 as mxaccess__gateway__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15mxaccess_worker.proto\x12\x12mxaccess_worker.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x16mxaccess_gateway.proto\"\x95\x06\n\x0eWorkerEnvelope\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x04\x12\x16\n\x0e\x63orrelation_id\x18\x04 \x01(\t\x12\x39\n\rgateway_hello\x18\n \x01(\x0b\x32 .mxaccess_worker.v1.GatewayHelloH\x00\x12\x37\n\x0cworker_hello\x18\x0b \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerHelloH\x00\x12\x37\n\x0cworker_ready\x18\x0c \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerReadyH\x00\x12;\n\x0eworker_command\x18\r \x01(\x0b\x32!.mxaccess_worker.v1.WorkerCommandH\x00\x12\x46\n\x14worker_command_reply\x18\x0e \x01(\x0b\x32&.mxaccess_worker.v1.WorkerCommandReplyH\x00\x12\x39\n\rworker_cancel\x18\x0f \x01(\x0b\x32 .mxaccess_worker.v1.WorkerCancelH\x00\x12=\n\x0fworker_shutdown\x18\x10 \x01(\x0b\x32\".mxaccess_worker.v1.WorkerShutdownH\x00\x12\x44\n\x13worker_shutdown_ack\x18\x11 \x01(\x0b\x32%.mxaccess_worker.v1.WorkerShutdownAckH\x00\x12\x37\n\x0cworker_event\x18\x12 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerEventH\x00\x12?\n\x10worker_heartbeat\x18\x13 \x01(\x0b\x32#.mxaccess_worker.v1.WorkerHeartbeatH\x00\x12\x37\n\x0cworker_fault\x18\x14 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerFaultH\x00\x42\x06\n\x04\x62ody\"Z\n\x0cGatewayHello\x12\"\n\x1asupported_protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x17\n\x0fgateway_version\x18\x03 \x01(\t\"i\n\x0bWorkerHello\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x19\n\x11worker_process_id\x18\x03 \x01(\x05\x12\x16\n\x0eworker_version\x18\x04 \x01(\t\"\x8e\x01\n\x0bWorkerReady\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12\x17\n\x0fmxaccess_progid\x18\x02 \x01(\t\x12\x16\n\x0emxaccess_clsid\x18\x03 \x01(\t\x12\x33\n\x0fready_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"w\n\rWorkerCommand\x12/\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x1e.mxaccess_gateway.v1.MxCommand\x12\x35\n\x11\x65nqueue_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x81\x01\n\x12WorkerCommandReply\x12\x32\n\x05reply\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.MxCommandReply\x12\x37\n\x13\x63ompleted_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x1e\n\x0cWorkerCancel\x12\x0e\n\x06reason\x18\x01 \x01(\t\"Q\n\x0eWorkerShutdown\x12/\n\x0cgrace_period\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x0e\n\x06reason\x18\x02 \x01(\t\"H\n\x11WorkerShutdownAck\x12\x33\n\x06status\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatus\":\n\x0bWorkerEvent\x12+\n\x05\x65vent\x18\x01 \x01(\x0b\x32\x1c.mxaccess_gateway.v1.MxEvent\"\xa5\x02\n\x0fWorkerHeartbeat\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12.\n\x05state\x18\x02 \x01(\x0e\x32\x1f.mxaccess_worker.v1.WorkerState\x12?\n\x1blast_sta_activity_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15pending_command_count\x18\x04 \x01(\r\x12\"\n\x1aoutbound_event_queue_depth\x18\x05 \x01(\r\x12\x1b\n\x13last_event_sequence\x18\x06 \x01(\x04\x12&\n\x1e\x63urrent_command_correlation_id\x18\x07 \x01(\t\"\xf4\x01\n\x0bWorkerFault\x12\x39\n\x08\x63\x61tegory\x18\x01 \x01(\x0e\x32\'.mxaccess_worker.v1.WorkerFaultCategory\x12\x16\n\x0e\x63ommand_method\x18\x02 \x01(\t\x12\x14\n\x07hresult\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\x0e\x65xception_type\x18\x04 \x01(\t\x12\x1a\n\x12\x64iagnostic_message\x18\x05 \x01(\t\x12<\n\x0fprotocol_status\x18\x06 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatusB\n\n\x08_hresult*\x97\x02\n\x0bWorkerState\x12\x1c\n\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x19\n\x15WORKER_STATE_STARTING\x10\x01\x12\x1c\n\x18WORKER_STATE_HANDSHAKING\x10\x02\x12!\n\x1dWORKER_STATE_INITIALIZING_STA\x10\x03\x12\x16\n\x12WORKER_STATE_READY\x10\x04\x12\"\n\x1eWORKER_STATE_EXECUTING_COMMAND\x10\x05\x12\x1e\n\x1aWORKER_STATE_SHUTTING_DOWN\x10\x06\x12\x18\n\x14WORKER_STATE_STOPPED\x10\x07\x12\x18\n\x14WORKER_STATE_FAULTED\x10\x08*\xc7\x04\n\x13WorkerFaultCategory\x12%\n!WORKER_FAULT_CATEGORY_UNSPECIFIED\x10\x00\x12+\n\'WORKER_FAULT_CATEGORY_INVALID_ARGUMENTS\x10\x01\x12\x37\n3WORKER_FAULT_CATEGORY_GATEWAY_AUTHENTICATION_FAILED\x10\x02\x12+\n\'WORKER_FAULT_CATEGORY_PROTOCOL_MISMATCH\x10\x03\x12,\n(WORKER_FAULT_CATEGORY_PROTOCOL_VIOLATION\x10\x04\x12+\n\'WORKER_FAULT_CATEGORY_PIPE_DISCONNECTED\x10\x05\x12\x32\n.WORKER_FAULT_CATEGORY_MXACCESS_CREATION_FAILED\x10\x06\x12\x31\n-WORKER_FAULT_CATEGORY_MXACCESS_COMMAND_FAILED\x10\x07\x12:\n6WORKER_FAULT_CATEGORY_MXACCESS_EVENT_CONVERSION_FAILED\x10\x08\x12\"\n\x1eWORKER_FAULT_CATEGORY_STA_HUNG\x10\t\x12(\n$WORKER_FAULT_CATEGORY_QUEUE_OVERFLOW\x10\n\x12*\n&WORKER_FAULT_CATEGORY_SHUTDOWN_TIMEOUT\x10\x0b\x42&\xaa\x02#ZB.MOM.WW.MxGateway.Contracts.Protob\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mxaccess_worker_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options = b'\252\002#ZB.MOM.WW.MxGateway.Contracts.Proto'
_globals['_WORKERSTATE']._serialized_start=2316
_globals['_WORKERSTATE']._serialized_end=2595
_globals['_WORKERFAULTCATEGORY']._serialized_start=2598
_globals['_WORKERFAULTCATEGORY']._serialized_end=3181
_globals['_WORKERENVELOPE']._serialized_start=135
_globals['_WORKERENVELOPE']._serialized_end=924
_globals['_GATEWAYHELLO']._serialized_start=926
_globals['_GATEWAYHELLO']._serialized_end=1016
_globals['_WORKERHELLO']._serialized_start=1018
_globals['_WORKERHELLO']._serialized_end=1123
_globals['_WORKERREADY']._serialized_start=1126
_globals['_WORKERREADY']._serialized_end=1268
_globals['_WORKERCOMMAND']._serialized_start=1270
_globals['_WORKERCOMMAND']._serialized_end=1389
_globals['_WORKERCOMMANDREPLY']._serialized_start=1392
_globals['_WORKERCOMMANDREPLY']._serialized_end=1521
_globals['_WORKERCANCEL']._serialized_start=1523
_globals['_WORKERCANCEL']._serialized_end=1553
_globals['_WORKERSHUTDOWN']._serialized_start=1555
_globals['_WORKERSHUTDOWN']._serialized_end=1636
_globals['_WORKERSHUTDOWNACK']._serialized_start=1638
_globals['_WORKERSHUTDOWNACK']._serialized_end=1710
_globals['_WORKEREVENT']._serialized_start=1712
_globals['_WORKEREVENT']._serialized_end=1770
_globals['_WORKERHEARTBEAT']._serialized_start=1773
_globals['_WORKERHEARTBEAT']._serialized_end=2066
_globals['_WORKERFAULT']._serialized_start=2069
_globals['_WORKERFAULT']._serialized_end=2313
# @@protoc_insertion_point(module_scope)
@@ -0,0 +1,24 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
GRPC_GENERATED_VERSION = '1.80.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in mxaccess_worker_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
@@ -0,0 +1,76 @@
"""Client connection options for the async Python wrapper."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import grpc
from .auth import REDACTED, ApiKey
@dataclass(frozen=True)
class ClientOptions:
"""Connection settings for `GatewayClient.connect`."""
endpoint: str
api_key: str | ApiKey | None = None
plaintext: bool = False
ca_file: str | None = None
server_name_override: str | None = None
call_timeout: float | None = 30.0
stream_timeout: float | None = None
max_grpc_message_bytes: int = 16 * 1024 * 1024
def __post_init__(self) -> None:
"""Validate options; raise `ValueError` for invalid combinations."""
if not self.endpoint:
raise ValueError("endpoint must not be empty")
if self.plaintext and self.ca_file:
raise ValueError("ca_file cannot be used with plaintext connections")
if self.call_timeout is not None and self.call_timeout <= 0:
raise ValueError("call_timeout must be greater than zero")
if self.stream_timeout is not None and self.stream_timeout <= 0:
raise ValueError("stream_timeout must be greater than zero")
if self.max_grpc_message_bytes <= 0:
raise ValueError("max_grpc_message_bytes must be greater than zero")
def __repr__(self) -> str:
"""Return a repr that redacts the API key value."""
api_key = REDACTED if self.api_key else None
return (
f"{type(self).__name__}(endpoint={self.endpoint!r}, "
f"api_key={api_key!r}, plaintext={self.plaintext!r}, "
f"ca_file={self.ca_file!r}, "
f"server_name_override={self.server_name_override!r}, "
f"call_timeout={self.call_timeout!r}, "
f"stream_timeout={self.stream_timeout!r}, "
f"max_grpc_message_bytes={self.max_grpc_message_bytes!r})"
)
def create_channel(options: ClientOptions) -> grpc.aio.Channel:
"""Create a plaintext or TLS `grpc.aio` channel from client options."""
channel_options: list[tuple[str, str | int]] = [
("grpc.max_receive_message_length", options.max_grpc_message_bytes),
("grpc.max_send_message_length", options.max_grpc_message_bytes),
]
if options.server_name_override:
channel_options.append(("grpc.ssl_target_name_override", options.server_name_override))
if options.plaintext:
return grpc.aio.insecure_channel(options.endpoint, options=channel_options)
root_certificates = None
if options.ca_file:
root_certificates = Path(options.ca_file).read_bytes()
credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates)
return grpc.aio.secure_channel(
options.endpoint,
credentials,
options=channel_options,
)
@@ -0,0 +1,404 @@
"""Async session wrapper for MXAccess Gateway commands."""
from __future__ import annotations
from collections.abc import AsyncIterator, Sequence
from .errors import ensure_mxaccess_success
from .generated import mxaccess_gateway_pb2 as pb
from .values import MxValueInput, to_mx_value
MAX_BULK_ITEMS = 1000
class Session:
"""A single gateway-backed MXAccess session."""
def __init__(
self,
*,
client: "GatewayClient",
session_id: str,
open_reply: pb.OpenSessionReply | None = None,
) -> None:
"""Initialize a session bound to a client and gateway session id."""
self.client = client
self.session_id = session_id
self.open_reply = open_reply
self._closed = False
async def __aenter__(self) -> "Session":
"""Return self to support ``async with`` usage."""
return self
async def __aexit__(self, *_exc_info: object) -> None:
"""Close the session when leaving an ``async with`` block."""
await self.close()
async def close(self, *, client_correlation_id: str = "") -> pb.CloseSessionReply:
"""Close the gateway session. Repeated calls return a local closed reply."""
if self._closed:
return pb.CloseSessionReply(
session_id=self.session_id,
final_state=pb.SESSION_STATE_CLOSED,
protocol_status=pb.ProtocolStatus(code=pb.PROTOCOL_STATUS_CODE_OK),
)
reply = await self.client.close_session_raw(
pb.CloseSessionRequest(
session_id=self.session_id,
client_correlation_id=client_correlation_id,
),
)
self._closed = True
return reply
async def invoke(self, command: pb.MxCommand, *, correlation_id: str = "") -> pb.MxCommandReply:
"""Invoke a raw command and enforce gateway and MXAccess success."""
reply = await self.invoke_raw(command, correlation_id=correlation_id)
return ensure_mxaccess_success("invoke", reply)
async def invoke_raw(
self,
command: pb.MxCommand,
*,
correlation_id: str = "",
) -> pb.MxCommandReply:
"""Invoke a raw command and preserve the raw reply."""
return await self.client.invoke_raw(
pb.MxCommandRequest(
session_id=self.session_id,
client_correlation_id=correlation_id,
command=command,
),
)
async def register(self, client_name: str, *, correlation_id: str = "") -> int:
"""Invoke MXAccess `Register` and return the new `ServerHandle`."""
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_REGISTER,
register=pb.RegisterCommand(client_name=client_name),
),
correlation_id=correlation_id,
)
return reply.register.server_handle
async def unregister(self, server_handle: int, *, correlation_id: str = "") -> None:
"""Invoke MXAccess `Unregister` for a previously registered `ServerHandle`."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_UNREGISTER,
unregister=pb.UnregisterCommand(server_handle=server_handle),
),
correlation_id=correlation_id,
)
async def remove_item(
self,
server_handle: int,
item_handle: int,
*,
correlation_id: str = "",
) -> None:
"""Invoke MXAccess `RemoveItem` for the given `ItemHandle`."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_REMOVE_ITEM,
remove_item=pb.RemoveItemCommand(
server_handle=server_handle,
item_handle=item_handle,
),
),
correlation_id=correlation_id,
)
async def add_item(
self,
server_handle: int,
item_definition: str,
*,
correlation_id: str = "",
) -> int:
"""Invoke MXAccess `AddItem` and return the new `ItemHandle`."""
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADD_ITEM,
add_item=pb.AddItemCommand(
server_handle=server_handle,
item_definition=item_definition,
),
),
correlation_id=correlation_id,
)
return reply.add_item.item_handle
async def add_item2(
self,
server_handle: int,
item_definition: str,
item_context: str,
*,
correlation_id: str = "",
) -> int:
"""Invoke MXAccess `AddItem2` with item context and return the new `ItemHandle`."""
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADD_ITEM2,
add_item2=pb.AddItem2Command(
server_handle=server_handle,
item_definition=item_definition,
item_context=item_context,
),
),
correlation_id=correlation_id,
)
return reply.add_item2.item_handle
async def advise(
self,
server_handle: int,
item_handle: int,
*,
correlation_id: str = "",
) -> None:
"""Invoke MXAccess `Advise` to subscribe an existing `ItemHandle` to events."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADVISE,
advise=pb.AdviseCommand(
server_handle=server_handle,
item_handle=item_handle,
),
),
correlation_id=correlation_id,
)
async def unadvise(
self,
server_handle: int,
item_handle: int,
*,
correlation_id: str = "",
) -> None:
"""Invoke MXAccess `UnAdvise` to stop event delivery for an `ItemHandle`."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_UN_ADVISE,
un_advise=pb.UnAdviseCommand(
server_handle=server_handle,
item_handle=item_handle,
),
),
correlation_id=correlation_id,
)
async def add_item_bulk(
self,
server_handle: int,
tag_addresses: Sequence[str],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `AddItemBulk` and return one result per tag address."""
if tag_addresses is None:
raise TypeError("tag_addresses is required")
_ensure_bulk_size("tag_addresses", len(tag_addresses))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADD_ITEM_BULK,
add_item_bulk=pb.AddItemBulkCommand(
server_handle=server_handle,
tag_addresses=tag_addresses,
),
),
correlation_id=correlation_id,
)
return list(reply.add_item_bulk.results)
async def advise_item_bulk(
self,
server_handle: int,
item_handles: Sequence[int],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `AdviseItemBulk` and return one result per item handle."""
if item_handles is None:
raise TypeError("item_handles is required")
_ensure_bulk_size("item_handles", len(item_handles))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADVISE_ITEM_BULK,
advise_item_bulk=pb.AdviseItemBulkCommand(
server_handle=server_handle,
item_handles=item_handles,
),
),
correlation_id=correlation_id,
)
return list(reply.advise_item_bulk.results)
async def remove_item_bulk(
self,
server_handle: int,
item_handles: Sequence[int],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `RemoveItemBulk` and return one result per item handle."""
if item_handles is None:
raise TypeError("item_handles is required")
_ensure_bulk_size("item_handles", len(item_handles))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_REMOVE_ITEM_BULK,
remove_item_bulk=pb.RemoveItemBulkCommand(
server_handle=server_handle,
item_handles=item_handles,
),
),
correlation_id=correlation_id,
)
return list(reply.remove_item_bulk.results)
async def unadvise_item_bulk(
self,
server_handle: int,
item_handles: Sequence[int],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `UnAdviseItemBulk` and return one result per item handle."""
if item_handles is None:
raise TypeError("item_handles is required")
_ensure_bulk_size("item_handles", len(item_handles))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_UN_ADVISE_ITEM_BULK,
un_advise_item_bulk=pb.UnAdviseItemBulkCommand(
server_handle=server_handle,
item_handles=item_handles,
),
),
correlation_id=correlation_id,
)
return list(reply.un_advise_item_bulk.results)
async def subscribe_bulk(
self,
server_handle: int,
tag_addresses: Sequence[str],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `SubscribeBulk` and return one result per tag address."""
if tag_addresses is None:
raise TypeError("tag_addresses is required")
_ensure_bulk_size("tag_addresses", len(tag_addresses))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_SUBSCRIBE_BULK,
subscribe_bulk=pb.SubscribeBulkCommand(
server_handle=server_handle,
tag_addresses=tag_addresses,
),
),
correlation_id=correlation_id,
)
return list(reply.subscribe_bulk.results)
async def unsubscribe_bulk(
self,
server_handle: int,
item_handles: Sequence[int],
*,
correlation_id: str = "",
) -> list[pb.SubscribeResult]:
"""Invoke MXAccess `UnsubscribeBulk` and return one result per item handle."""
if item_handles is None:
raise TypeError("item_handles is required")
_ensure_bulk_size("item_handles", len(item_handles))
reply = await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_UNSUBSCRIBE_BULK,
unsubscribe_bulk=pb.UnsubscribeBulkCommand(
server_handle=server_handle,
item_handles=item_handles,
),
),
correlation_id=correlation_id,
)
return list(reply.unsubscribe_bulk.results)
async def write(
self,
server_handle: int,
item_handle: int,
value: MxValueInput,
*,
user_id: int = 0,
correlation_id: str = "",
) -> None:
"""Invoke MXAccess `Write` for an `ItemHandle` with the converted value."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_WRITE,
write=pb.WriteCommand(
server_handle=server_handle,
item_handle=item_handle,
value=to_mx_value(value),
user_id=user_id,
),
),
correlation_id=correlation_id,
)
async def write2(
self,
server_handle: int,
item_handle: int,
value: MxValueInput,
timestamp_value: MxValueInput,
*,
user_id: int = 0,
correlation_id: str = "",
) -> None:
"""Invoke MXAccess `Write2` with both a value and a client-supplied timestamp."""
await self.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_WRITE2,
write2=pb.Write2Command(
server_handle=server_handle,
item_handle=item_handle,
value=to_mx_value(value),
timestamp_value=to_mx_value(timestamp_value),
user_id=user_id,
),
),
correlation_id=correlation_id,
)
def stream_events(
self,
*,
after_worker_sequence: int = 0,
) -> AsyncIterator[pb.MxEvent]:
"""Return an async iterator of `MxEvent` messages for this session."""
return self.client.stream_events_raw(
pb.StreamEventsRequest(
session_id=self.session_id,
after_worker_sequence=after_worker_sequence,
),
)
def _ensure_bulk_size(name: str, count: int) -> None:
if count > MAX_BULK_ITEMS:
raise ValueError(f"{name} bulk commands are limited to {MAX_BULK_ITEMS} item(s)")
from .client import GatewayClient # noqa: E402
@@ -0,0 +1,234 @@
"""MXAccess value conversion helpers."""
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any
from google.protobuf.timestamp_pb2 import Timestamp
from .generated import mxaccess_gateway_pb2 as pb
MxValueInput = bool | int | float | str | datetime | bytes | None | Sequence[Any]
@dataclass(frozen=True)
class MxValueView:
"""Typed projection of a raw `MxValue` protobuf message."""
value: Any
kind: str
raw: pb.MxValue
def to_mx_value(value: MxValueInput, *, data_type: str | None = None) -> pb.MxValue:
"""Convert a Python value into the public protobuf `MxValue` union."""
if isinstance(value, pb.MxValue):
return value
if value is None:
return pb.MxValue(
data_type=pb.MX_DATA_TYPE_NO_DATA,
variant_type="VT_EMPTY",
is_null=True,
raw_data_type=pb.MX_DATA_TYPE_NO_DATA,
)
if isinstance(value, bool):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_BOOLEAN),
variant_type="VT_BOOL",
bool_value=value,
)
if isinstance(value, int):
if -(2**31) <= value <= (2**31 - 1):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_INTEGER),
variant_type="VT_I4",
int32_value=value,
)
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_INTEGER),
variant_type="VT_I8",
int64_value=value,
)
if isinstance(value, float):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_DOUBLE),
variant_type="VT_R8",
double_value=value,
)
if isinstance(value, str):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_STRING),
variant_type="VT_BSTR",
string_value=value,
)
if isinstance(value, datetime):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_TIME),
variant_type="VT_DATE",
timestamp_value=_timestamp_from_datetime(value),
)
if isinstance(value, bytes):
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_UNKNOWN),
variant_type="VT_RECORD",
raw_value=value,
)
if isinstance(value, Sequence):
return _sequence_to_mx_value(value, data_type=data_type)
raise TypeError(f"unsupported MxValue input type: {type(value).__name__}")
def from_mx_value(value: pb.MxValue) -> MxValueView:
"""Project a protobuf `MxValue` into an idiomatic Python value."""
kind = value.WhichOneof("kind")
if kind is None:
return MxValueView(None, "none", value)
if kind == "timestamp_value":
return MxValueView(
value.timestamp_value.ToDatetime().replace(tzinfo=timezone.utc),
kind,
value,
)
if kind == "array_value":
return MxValueView(from_mx_array(value.array_value), kind, value)
return MxValueView(getattr(value, kind), kind, value)
def from_mx_array(array: pb.MxArray) -> list[Any]:
"""Project a protobuf `MxArray` into a Python list."""
kind = array.WhichOneof("values")
if kind is None:
return []
values = list(getattr(array, kind).values)
if kind == "timestamp_values":
return [
timestamp.ToDatetime().replace(tzinfo=timezone.utc)
for timestamp in values
]
return values
def _sequence_to_mx_value(
values: Sequence[Any],
*,
data_type: str | None,
) -> pb.MxValue:
sequence = list(values)
if not sequence:
return pb.MxValue(
data_type=_data_type(data_type, pb.MX_DATA_TYPE_UNKNOWN),
array_value=pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_UNKNOWN,
dimensions=[0],
),
)
first = sequence[0]
dimensions = [len(sequence)]
if all(isinstance(item, bool) for item in sequence):
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_BOOLEAN,
variant_type="VT_ARRAY|VT_BOOL",
dimensions=dimensions,
bool_values=pb.BoolArray(values=sequence),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_BOOLEAN, array_value=array)
if all(isinstance(item, int) and not isinstance(item, bool) for item in sequence):
use_int32 = all(-(2**31) <= item <= (2**31 - 1) for item in sequence)
if use_int32:
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_INTEGER,
variant_type="VT_ARRAY|VT_I4",
dimensions=dimensions,
int32_values=pb.Int32Array(values=sequence),
)
else:
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_INTEGER,
variant_type="VT_ARRAY|VT_I8",
dimensions=dimensions,
int64_values=pb.Int64Array(values=sequence),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_INTEGER, array_value=array)
if all(isinstance(item, float) for item in sequence):
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_DOUBLE,
variant_type="VT_ARRAY|VT_R8",
dimensions=dimensions,
double_values=pb.DoubleArray(values=sequence),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_DOUBLE, array_value=array)
if all(isinstance(item, str) for item in sequence):
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_STRING,
variant_type="VT_ARRAY|VT_BSTR",
dimensions=dimensions,
string_values=pb.StringArray(values=sequence),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_STRING, array_value=array)
if all(isinstance(item, datetime) for item in sequence):
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_TIME,
variant_type="VT_ARRAY|VT_DATE",
dimensions=dimensions,
timestamp_values=pb.TimestampArray(
values=[_timestamp_from_datetime(item) for item in sequence],
),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_TIME, array_value=array)
if all(isinstance(item, bytes) for item in sequence):
array = pb.MxArray(
element_data_type=pb.MX_DATA_TYPE_UNKNOWN,
variant_type="VT_ARRAY|VT_VARIANT",
dimensions=dimensions,
raw_values=pb.RawArray(values=sequence),
)
return pb.MxValue(data_type=pb.MX_DATA_TYPE_UNKNOWN, array_value=array)
raise TypeError(
"MxValue array inputs must use one supported element type; "
f"first element was {type(first).__name__}"
)
def _timestamp_from_datetime(value: datetime) -> Timestamp:
timestamp = Timestamp()
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
timestamp.FromDatetime(value.astimezone(timezone.utc))
return timestamp
def _data_type(name: str | None, default: int) -> int:
if name is None:
return default
return pb.MxDataType.Value(name)
@@ -0,0 +1,3 @@
"""Package version information."""
__version__ = "0.1.0"