aionetx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aionetx/__init__.py +65 -0
- aionetx/api/__init__.py +137 -0
- aionetx/api/_event_registry.py +182 -0
- aionetx/api/_validation.py +104 -0
- aionetx/api/base_network_event_handler.py +113 -0
- aionetx/api/byte_sender_protocol.py +35 -0
- aionetx/api/bytes_like.py +14 -0
- aionetx/api/bytes_received_event.py +30 -0
- aionetx/api/component_lifecycle_changed_event.py +30 -0
- aionetx/api/component_lifecycle_state.py +19 -0
- aionetx/api/connection_events.py +71 -0
- aionetx/api/connection_lifecycle.py +29 -0
- aionetx/api/connection_metadata.py +34 -0
- aionetx/api/connection_protocol.py +77 -0
- aionetx/api/diagnostics.py +49 -0
- aionetx/api/error_policy.py +18 -0
- aionetx/api/errors.py +32 -0
- aionetx/api/event_delivery_settings.py +90 -0
- aionetx/api/events.py +41 -0
- aionetx/api/handler_failure_policy_stop_event.py +34 -0
- aionetx/api/heartbeat.py +80 -0
- aionetx/api/heartbeat_provider_protocol.py +28 -0
- aionetx/api/lifecycle.py +19 -0
- aionetx/api/managed_transport_protocol.py +72 -0
- aionetx/api/multicast_receiver_protocol.py +22 -0
- aionetx/api/multicast_receiver_settings.py +88 -0
- aionetx/api/network_error_event.py +24 -0
- aionetx/api/network_event.py +53 -0
- aionetx/api/network_event_handler_protocol.py +20 -0
- aionetx/api/network_factory.py +130 -0
- aionetx/api/policies.py +26 -0
- aionetx/api/protocols.py +35 -0
- aionetx/api/reconnect_events.py +59 -0
- aionetx/api/reconnect_jitter.py +17 -0
- aionetx/api/settings.py +25 -0
- aionetx/api/tcp_client.py +165 -0
- aionetx/api/tcp_reconnect_settings.py +74 -0
- aionetx/api/tcp_server.py +175 -0
- aionetx/api/typed_event_router.py +87 -0
- aionetx/api/udp.py +193 -0
- aionetx/factories/__init__.py +10 -0
- aionetx/factories/asyncio_network_factory.py +141 -0
- aionetx/implementations/__init__.py +18 -0
- aionetx/implementations/asyncio_impl/__init__.py +16 -0
- aionetx/implementations/asyncio_impl/_asyncio_datagram_receiver_base.py +1024 -0
- aionetx/implementations/asyncio_impl/_datagram_receiver_state.py +112 -0
- aionetx/implementations/asyncio_impl/_event_dispatcher_policy.py +56 -0
- aionetx/implementations/asyncio_impl/_event_dispatcher_queue.py +43 -0
- aionetx/implementations/asyncio_impl/_tcp_client_connect.py +182 -0
- aionetx/implementations/asyncio_impl/_tcp_client_runtime.py +119 -0
- aionetx/implementations/asyncio_impl/_tcp_client_stop_state.py +55 -0
- aionetx/implementations/asyncio_impl/_tcp_connection_helpers.py +129 -0
- aionetx/implementations/asyncio_impl/_tcp_server_helpers.py +365 -0
- aionetx/implementations/asyncio_impl/_tcp_server_stop_state.py +56 -0
- aionetx/implementations/asyncio_impl/asyncio_heartbeat_sender.py +187 -0
- aionetx/implementations/asyncio_impl/asyncio_multicast_receiver.py +158 -0
- aionetx/implementations/asyncio_impl/asyncio_tcp_client.py +809 -0
- aionetx/implementations/asyncio_impl/asyncio_tcp_connection.py +714 -0
- aionetx/implementations/asyncio_impl/asyncio_tcp_server.py +803 -0
- aionetx/implementations/asyncio_impl/asyncio_udp_receiver.py +124 -0
- aionetx/implementations/asyncio_impl/asyncio_udp_sender.py +240 -0
- aionetx/implementations/asyncio_impl/event_dispatcher.py +829 -0
- aionetx/implementations/asyncio_impl/identifier_utils.py +57 -0
- aionetx/implementations/asyncio_impl/lifecycle_internal.py +157 -0
- aionetx/implementations/asyncio_impl/runtime_utils.py +280 -0
- aionetx/implementations/asyncio_impl/tcp_client_supervision.py +395 -0
- aionetx/py.typed +0 -0
- aionetx/testing/__init__.py +115 -0
- aionetx-0.1.0.dist-info/METADATA +952 -0
- aionetx-0.1.0.dist-info/RECORD +73 -0
- aionetx-0.1.0.dist-info/WHEEL +5 -0
- aionetx-0.1.0.dist-info/licenses/LICENSE +21 -0
- aionetx-0.1.0.dist-info/top_level.txt +1 -0
aionetx/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
aionetx — asyncio-first transport library for TCP and UDP networking.
|
|
3
|
+
|
|
4
|
+
This package exposes a curated, stable public API via :data:`PUBLIC_API`.
|
|
5
|
+
The recommended entry point for application code is
|
|
6
|
+
:class:`AsyncioNetworkFactory`, which produces managed TCP/UDP transports
|
|
7
|
+
driven by dataclass-based settings (``TcpClientSettings``, ``TcpServerSettings``,
|
|
8
|
+
``UdpReceiverSettings``, ``UdpSenderSettings``, ``MulticastReceiverSettings``).
|
|
9
|
+
|
|
10
|
+
Advanced event-handler composition helpers live alongside the factory
|
|
11
|
+
(``BaseNetworkEventHandler``, ``NetworkEvent``, ``BytesReceivedEvent``).
|
|
12
|
+
Transport-protocol interfaces, concrete event subclasses, and additional
|
|
13
|
+
errors are available from :mod:`aionetx.api` for users who need the full
|
|
14
|
+
curated surface. Anything under :mod:`aionetx.implementations` is internal.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from importlib.metadata import version as _metadata_version
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
__version__: str = _metadata_version("aionetx")
|
|
21
|
+
except Exception: # pragma: no cover — package not installed in editable mode rarely
|
|
22
|
+
__version__ = "0.0.0.dev0"
|
|
23
|
+
|
|
24
|
+
from aionetx.api import (
|
|
25
|
+
BaseNetworkEventHandler,
|
|
26
|
+
BytesReceivedEvent,
|
|
27
|
+
ComponentLifecycleState,
|
|
28
|
+
EventDeliverySettings,
|
|
29
|
+
HeartbeatConfigurationError,
|
|
30
|
+
HeartbeatProviderProtocol,
|
|
31
|
+
MulticastReceiverSettings,
|
|
32
|
+
NetworkEvent,
|
|
33
|
+
TcpHeartbeatSettings,
|
|
34
|
+
TcpReconnectSettings,
|
|
35
|
+
TcpClientSettings,
|
|
36
|
+
TcpServerSettings,
|
|
37
|
+
UdpReceiverSettings,
|
|
38
|
+
UdpSenderSettings,
|
|
39
|
+
)
|
|
40
|
+
from aionetx.factories import AsyncioNetworkFactory
|
|
41
|
+
|
|
42
|
+
# Public API governance:
|
|
43
|
+
# - `__all__` is the canonical curated export list for this module.
|
|
44
|
+
# - `PUBLIC_API` is derived from `__all__` for downstream introspection/testing.
|
|
45
|
+
# - `aionetx.implementations.*` remains internal/advanced and may change.
|
|
46
|
+
__all__ = (
|
|
47
|
+
"__version__",
|
|
48
|
+
"AsyncioNetworkFactory",
|
|
49
|
+
"BaseNetworkEventHandler",
|
|
50
|
+
"BytesReceivedEvent",
|
|
51
|
+
"ComponentLifecycleState",
|
|
52
|
+
"EventDeliverySettings",
|
|
53
|
+
"HeartbeatConfigurationError",
|
|
54
|
+
"HeartbeatProviderProtocol",
|
|
55
|
+
"TcpHeartbeatSettings",
|
|
56
|
+
"MulticastReceiverSettings",
|
|
57
|
+
"NetworkEvent",
|
|
58
|
+
"TcpReconnectSettings",
|
|
59
|
+
"TcpClientSettings",
|
|
60
|
+
"TcpServerSettings",
|
|
61
|
+
"UdpReceiverSettings",
|
|
62
|
+
"UdpSenderSettings",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
PUBLIC_API = tuple(__all__)
|
aionetx/api/__init__.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Curated advanced API surface for ``aionetx``.
|
|
3
|
+
|
|
4
|
+
This package groups public event types, lifecycle models, protocols, settings,
|
|
5
|
+
and selected exceptions for users who want more control than the package-root
|
|
6
|
+
imports provide.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from aionetx.api.events import (
|
|
10
|
+
BaseNetworkEventHandler,
|
|
11
|
+
BytesReceivedEvent,
|
|
12
|
+
ComponentLifecycleChangedEvent,
|
|
13
|
+
ConnectionClosedEvent,
|
|
14
|
+
ConnectionOpenedEvent,
|
|
15
|
+
ConnectionRejectedEvent,
|
|
16
|
+
HandlerFailurePolicyStopEvent,
|
|
17
|
+
NetworkErrorEvent,
|
|
18
|
+
NetworkEvent,
|
|
19
|
+
ReconnectAttemptFailedEvent,
|
|
20
|
+
ReconnectAttemptStartedEvent,
|
|
21
|
+
ReconnectScheduledEvent,
|
|
22
|
+
)
|
|
23
|
+
from aionetx.api.lifecycle import (
|
|
24
|
+
ComponentLifecycleState,
|
|
25
|
+
ConnectionMetadata,
|
|
26
|
+
ConnectionRole,
|
|
27
|
+
ConnectionState,
|
|
28
|
+
)
|
|
29
|
+
from aionetx.api.diagnostics import DispatcherRuntimeStats
|
|
30
|
+
from aionetx.api.errors import (
|
|
31
|
+
ConnectionClosedError,
|
|
32
|
+
HeartbeatConfigurationError,
|
|
33
|
+
InvalidNetworkConfigurationError,
|
|
34
|
+
NetworkConfigurationError,
|
|
35
|
+
NetworkLayerError,
|
|
36
|
+
NetworkRuntimeError,
|
|
37
|
+
)
|
|
38
|
+
from aionetx.api.heartbeat import HeartbeatRequest, HeartbeatResult, TcpHeartbeatSettings
|
|
39
|
+
from aionetx.api.network_event_handler_protocol import NetworkEventHandlerProtocol
|
|
40
|
+
from aionetx.api.policies import (
|
|
41
|
+
ErrorPolicy,
|
|
42
|
+
EventBackpressurePolicy,
|
|
43
|
+
EventDeliverySettings,
|
|
44
|
+
EventDispatchMode,
|
|
45
|
+
EventHandlerFailurePolicy,
|
|
46
|
+
ReconnectJitter,
|
|
47
|
+
)
|
|
48
|
+
from aionetx.api.protocols import (
|
|
49
|
+
ByteSenderProtocol,
|
|
50
|
+
BytesLike,
|
|
51
|
+
ConnectionProtocol,
|
|
52
|
+
HeartbeatProviderProtocol,
|
|
53
|
+
ManagedTransportProtocol,
|
|
54
|
+
MulticastReceiverProtocol,
|
|
55
|
+
NetworkFactory,
|
|
56
|
+
TcpClientProtocol,
|
|
57
|
+
TcpServerProtocol,
|
|
58
|
+
UdpReceiverProtocol,
|
|
59
|
+
UdpSenderProtocol,
|
|
60
|
+
)
|
|
61
|
+
from aionetx.api.settings import (
|
|
62
|
+
MulticastReceiverSettings,
|
|
63
|
+
TcpClientSettings,
|
|
64
|
+
TcpReconnectSettings,
|
|
65
|
+
TcpServerSettings,
|
|
66
|
+
UdpReceiverSettings,
|
|
67
|
+
UdpSenderSettings,
|
|
68
|
+
)
|
|
69
|
+
from aionetx.api.udp import (
|
|
70
|
+
UdpInvalidTargetError,
|
|
71
|
+
UdpSenderStoppedError,
|
|
72
|
+
)
|
|
73
|
+
from aionetx.api.typed_event_router import TypedEventRouter
|
|
74
|
+
|
|
75
|
+
# Public API governance:
|
|
76
|
+
# - ``__all__`` is the canonical curated export list for this module.
|
|
77
|
+
# - ``PUBLIC_API`` mirrors that list for tests and introspection helpers.
|
|
78
|
+
# - Underscore-prefixed modules remain internal unless explicitly promoted here.
|
|
79
|
+
# - Any type reachable from a public signature should be importable from this
|
|
80
|
+
# curated API surface so callers do not need to reach into internal modules.
|
|
81
|
+
__all__ = (
|
|
82
|
+
"BaseNetworkEventHandler",
|
|
83
|
+
"ByteSenderProtocol",
|
|
84
|
+
"BytesLike",
|
|
85
|
+
"BytesReceivedEvent",
|
|
86
|
+
"ComponentLifecycleChangedEvent",
|
|
87
|
+
"ComponentLifecycleState",
|
|
88
|
+
"ConnectionClosedError",
|
|
89
|
+
"ConnectionClosedEvent",
|
|
90
|
+
"ConnectionMetadata",
|
|
91
|
+
"ConnectionOpenedEvent",
|
|
92
|
+
"ConnectionRejectedEvent",
|
|
93
|
+
"ConnectionProtocol",
|
|
94
|
+
"ConnectionRole",
|
|
95
|
+
"ConnectionState",
|
|
96
|
+
"DispatcherRuntimeStats",
|
|
97
|
+
"ErrorPolicy",
|
|
98
|
+
"EventBackpressurePolicy",
|
|
99
|
+
"EventDeliverySettings",
|
|
100
|
+
"EventDispatchMode",
|
|
101
|
+
"EventHandlerFailurePolicy",
|
|
102
|
+
"HandlerFailurePolicyStopEvent",
|
|
103
|
+
"HeartbeatConfigurationError",
|
|
104
|
+
"HeartbeatProviderProtocol",
|
|
105
|
+
"HeartbeatRequest",
|
|
106
|
+
"HeartbeatResult",
|
|
107
|
+
"TcpHeartbeatSettings",
|
|
108
|
+
"InvalidNetworkConfigurationError",
|
|
109
|
+
"ManagedTransportProtocol",
|
|
110
|
+
"MulticastReceiverProtocol",
|
|
111
|
+
"MulticastReceiverSettings",
|
|
112
|
+
"NetworkConfigurationError",
|
|
113
|
+
"NetworkFactory",
|
|
114
|
+
"NetworkErrorEvent",
|
|
115
|
+
"NetworkEvent",
|
|
116
|
+
"NetworkEventHandlerProtocol",
|
|
117
|
+
"NetworkLayerError",
|
|
118
|
+
"NetworkRuntimeError",
|
|
119
|
+
"ReconnectAttemptFailedEvent",
|
|
120
|
+
"ReconnectAttemptStartedEvent",
|
|
121
|
+
"ReconnectJitter",
|
|
122
|
+
"ReconnectScheduledEvent",
|
|
123
|
+
"TcpReconnectSettings",
|
|
124
|
+
"TcpClientProtocol",
|
|
125
|
+
"TcpClientSettings",
|
|
126
|
+
"TcpServerProtocol",
|
|
127
|
+
"TcpServerSettings",
|
|
128
|
+
"TypedEventRouter",
|
|
129
|
+
"UdpInvalidTargetError",
|
|
130
|
+
"UdpReceiverProtocol",
|
|
131
|
+
"UdpReceiverSettings",
|
|
132
|
+
"UdpSenderProtocol",
|
|
133
|
+
"UdpSenderSettings",
|
|
134
|
+
"UdpSenderStoppedError",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
PUBLIC_API = tuple(__all__)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal taxonomy registry for all emitted network event types.
|
|
3
|
+
|
|
4
|
+
The registry in this module is the single source of truth for the open
|
|
5
|
+
``NetworkEvent`` union, typed hook names, and recording bucket names used by
|
|
6
|
+
testing helpers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from collections.abc import Awaitable, Callable
|
|
13
|
+
from typing import Any, TypeAlias, get_args
|
|
14
|
+
|
|
15
|
+
from aionetx.api.bytes_received_event import BytesReceivedEvent
|
|
16
|
+
from aionetx.api.component_lifecycle_changed_event import ComponentLifecycleChangedEvent
|
|
17
|
+
from aionetx.api.connection_events import (
|
|
18
|
+
ConnectionClosedEvent,
|
|
19
|
+
ConnectionOpenedEvent,
|
|
20
|
+
ConnectionRejectedEvent,
|
|
21
|
+
)
|
|
22
|
+
from aionetx.api.handler_failure_policy_stop_event import HandlerFailurePolicyStopEvent
|
|
23
|
+
from aionetx.api.network_error_event import NetworkErrorEvent
|
|
24
|
+
from aionetx.api.reconnect_events import (
|
|
25
|
+
ReconnectAttemptFailedEvent,
|
|
26
|
+
ReconnectAttemptStartedEvent,
|
|
27
|
+
ReconnectScheduledEvent,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
NetworkEvent: TypeAlias = (
|
|
31
|
+
ConnectionOpenedEvent
|
|
32
|
+
| ConnectionRejectedEvent
|
|
33
|
+
| ConnectionClosedEvent
|
|
34
|
+
| BytesReceivedEvent
|
|
35
|
+
| NetworkErrorEvent
|
|
36
|
+
| HandlerFailurePolicyStopEvent
|
|
37
|
+
| ComponentLifecycleChangedEvent
|
|
38
|
+
| ReconnectAttemptStartedEvent
|
|
39
|
+
| ReconnectAttemptFailedEvent
|
|
40
|
+
| ReconnectScheduledEvent
|
|
41
|
+
)
|
|
42
|
+
EventType: TypeAlias = type[NetworkEvent]
|
|
43
|
+
EventHandlerMethod: TypeAlias = Callable[[Any], Awaitable[None]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class EventRegistryEntry:
|
|
48
|
+
"""Canonical taxonomy entry for one network event type."""
|
|
49
|
+
|
|
50
|
+
event_type: EventType
|
|
51
|
+
handler_method_name: str
|
|
52
|
+
recording_bucket_name: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
NETWORK_EVENT_REGISTRY: tuple[EventRegistryEntry, ...] = (
|
|
56
|
+
EventRegistryEntry(
|
|
57
|
+
event_type=ConnectionOpenedEvent,
|
|
58
|
+
handler_method_name="on_connection_opened",
|
|
59
|
+
recording_bucket_name="opened_events",
|
|
60
|
+
),
|
|
61
|
+
EventRegistryEntry(
|
|
62
|
+
event_type=ConnectionRejectedEvent,
|
|
63
|
+
handler_method_name="on_connection_rejected",
|
|
64
|
+
recording_bucket_name="rejected_events",
|
|
65
|
+
),
|
|
66
|
+
EventRegistryEntry(
|
|
67
|
+
event_type=ConnectionClosedEvent,
|
|
68
|
+
handler_method_name="on_connection_closed",
|
|
69
|
+
recording_bucket_name="closed_events",
|
|
70
|
+
),
|
|
71
|
+
EventRegistryEntry(
|
|
72
|
+
event_type=BytesReceivedEvent,
|
|
73
|
+
handler_method_name="on_bytes_received",
|
|
74
|
+
recording_bucket_name="received_events",
|
|
75
|
+
),
|
|
76
|
+
EventRegistryEntry(
|
|
77
|
+
event_type=NetworkErrorEvent,
|
|
78
|
+
handler_method_name="on_error",
|
|
79
|
+
recording_bucket_name="error_events",
|
|
80
|
+
),
|
|
81
|
+
EventRegistryEntry(
|
|
82
|
+
event_type=HandlerFailurePolicyStopEvent,
|
|
83
|
+
handler_method_name="on_handler_failure_policy_stop",
|
|
84
|
+
recording_bucket_name="handler_failure_policy_stop_events",
|
|
85
|
+
),
|
|
86
|
+
EventRegistryEntry(
|
|
87
|
+
event_type=ComponentLifecycleChangedEvent,
|
|
88
|
+
handler_method_name="on_component_lifecycle_changed",
|
|
89
|
+
recording_bucket_name="lifecycle_events",
|
|
90
|
+
),
|
|
91
|
+
EventRegistryEntry(
|
|
92
|
+
event_type=ReconnectAttemptStartedEvent,
|
|
93
|
+
handler_method_name="on_reconnect_attempt_started",
|
|
94
|
+
recording_bucket_name="reconnect_attempt_started_events",
|
|
95
|
+
),
|
|
96
|
+
EventRegistryEntry(
|
|
97
|
+
event_type=ReconnectAttemptFailedEvent,
|
|
98
|
+
handler_method_name="on_reconnect_attempt_failed",
|
|
99
|
+
recording_bucket_name="reconnect_attempt_failed_events",
|
|
100
|
+
),
|
|
101
|
+
EventRegistryEntry(
|
|
102
|
+
event_type=ReconnectScheduledEvent,
|
|
103
|
+
handler_method_name="on_reconnect_scheduled",
|
|
104
|
+
recording_bucket_name="reconnect_scheduled_events",
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Keep ``NETWORK_EVENT_REGISTRY`` as the taxonomy source of truth. Conformance
|
|
109
|
+
# checks below ensure that the open ``NetworkEvent`` union, typed event hooks,
|
|
110
|
+
# and testing collectors stay aligned with the same event set.
|
|
111
|
+
|
|
112
|
+
NETWORK_EVENT_TYPES: tuple[EventType, ...] = tuple(
|
|
113
|
+
entry.event_type for entry in NETWORK_EVENT_REGISTRY
|
|
114
|
+
)
|
|
115
|
+
NETWORK_EVENT_HANDLER_METHODS: dict[EventType, str] = {
|
|
116
|
+
entry.event_type: entry.handler_method_name for entry in NETWORK_EVENT_REGISTRY
|
|
117
|
+
}
|
|
118
|
+
NETWORK_EVENT_RECORDING_BUCKETS: dict[EventType, str] = {
|
|
119
|
+
entry.event_type: entry.recording_bucket_name for entry in NETWORK_EVENT_REGISTRY
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_network_event_registry_conformance(
|
|
124
|
+
network_event_union: object,
|
|
125
|
+
registry: tuple[EventRegistryEntry, ...],
|
|
126
|
+
) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Validate that the event union and taxonomy registry describe the same types.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
network_event_union: Union object representing the public event taxonomy.
|
|
132
|
+
registry: Registry entries that define hook names and recording buckets.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
RuntimeError: If the union and registry have drifted apart.
|
|
136
|
+
"""
|
|
137
|
+
union_event_types = tuple(get_args(network_event_union))
|
|
138
|
+
registry_event_types = tuple(entry.event_type for entry in registry)
|
|
139
|
+
|
|
140
|
+
missing_from_registry = [
|
|
141
|
+
event_type.__name__
|
|
142
|
+
for event_type in union_event_types
|
|
143
|
+
if event_type not in registry_event_types
|
|
144
|
+
]
|
|
145
|
+
extra_in_registry = [
|
|
146
|
+
event_type.__name__
|
|
147
|
+
for event_type in registry_event_types
|
|
148
|
+
if event_type not in union_event_types
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
if not missing_from_registry and not extra_in_registry:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
message_parts: list[str] = [
|
|
155
|
+
"Network event registry drift detected.",
|
|
156
|
+
"Update `NetworkEvent` and `NETWORK_EVENT_REGISTRY` together.",
|
|
157
|
+
]
|
|
158
|
+
if missing_from_registry:
|
|
159
|
+
message_parts.append(
|
|
160
|
+
f"Missing registry entries for: {', '.join(sorted(missing_from_registry))}."
|
|
161
|
+
)
|
|
162
|
+
if extra_in_registry:
|
|
163
|
+
message_parts.append(
|
|
164
|
+
f"Registry contains events missing from the union: {', '.join(sorted(extra_in_registry))}."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
raise RuntimeError(" ".join(message_parts))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
validate_network_event_registry_conformance(NetworkEvent, NETWORK_EVENT_REGISTRY)
|
|
171
|
+
|
|
172
|
+
__all__ = (
|
|
173
|
+
"EventHandlerMethod",
|
|
174
|
+
"EventRegistryEntry",
|
|
175
|
+
"EventType",
|
|
176
|
+
"NETWORK_EVENT_HANDLER_METHODS",
|
|
177
|
+
"NETWORK_EVENT_RECORDING_BUCKETS",
|
|
178
|
+
"NETWORK_EVENT_REGISTRY",
|
|
179
|
+
"NETWORK_EVENT_TYPES",
|
|
180
|
+
"NetworkEvent",
|
|
181
|
+
"validate_network_event_registry_conformance",
|
|
182
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Internal validation helpers for public settings dataclasses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from numbers import Real
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
from aionetx.api.errors import InvalidNetworkConfigurationError
|
|
11
|
+
|
|
12
|
+
_TEnum = TypeVar("_TEnum", bound=Enum)
|
|
13
|
+
_ErrorType = type[Exception]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def require_bool(
|
|
17
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
18
|
+
) -> bool:
|
|
19
|
+
if type(value) is not bool:
|
|
20
|
+
raise error_type(f"{field_name} must be a bool.")
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def require_non_empty_str(
|
|
25
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
26
|
+
) -> str:
|
|
27
|
+
if not isinstance(value, str) or not value.strip():
|
|
28
|
+
raise error_type(f"{field_name} must not be empty or whitespace-only.")
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def require_optional_non_empty_str(
|
|
33
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
34
|
+
) -> str | None:
|
|
35
|
+
if value is None:
|
|
36
|
+
return None
|
|
37
|
+
return require_non_empty_str(field_name=field_name, value=value, error_type=error_type)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_int_range(
|
|
41
|
+
*,
|
|
42
|
+
field_name: str,
|
|
43
|
+
value: object,
|
|
44
|
+
min_value: int,
|
|
45
|
+
max_value: int,
|
|
46
|
+
error_type: _ErrorType = InvalidNetworkConfigurationError,
|
|
47
|
+
) -> int:
|
|
48
|
+
if type(value) is not int or not (min_value <= value <= max_value):
|
|
49
|
+
raise error_type(f"{field_name} must be between {min_value} and {max_value}.")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def require_positive_int(
|
|
54
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
55
|
+
) -> int:
|
|
56
|
+
if type(value) is not int or value <= 0:
|
|
57
|
+
raise error_type(f"{field_name} must be > 0.")
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def require_positive_finite_number(
|
|
62
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
63
|
+
) -> float:
|
|
64
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
65
|
+
raise error_type(f"{field_name} must be a finite number > 0.")
|
|
66
|
+
number = float(value)
|
|
67
|
+
if not math.isfinite(number) or number <= 0:
|
|
68
|
+
raise error_type(f"{field_name} must be a finite number > 0.")
|
|
69
|
+
return number
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def require_optional_positive_finite_number(
|
|
73
|
+
*, field_name: str, value: object, error_type: _ErrorType = InvalidNetworkConfigurationError
|
|
74
|
+
) -> float | None:
|
|
75
|
+
if value is None:
|
|
76
|
+
return None
|
|
77
|
+
return require_positive_finite_number(field_name=field_name, value=value, error_type=error_type)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def require_finite_number_at_least(
|
|
81
|
+
*,
|
|
82
|
+
field_name: str,
|
|
83
|
+
value: object,
|
|
84
|
+
minimum: float,
|
|
85
|
+
error_type: _ErrorType = InvalidNetworkConfigurationError,
|
|
86
|
+
) -> float:
|
|
87
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
88
|
+
raise error_type(f"{field_name} must be a finite number >= {minimum}.")
|
|
89
|
+
number = float(value)
|
|
90
|
+
if not math.isfinite(number) or number < minimum:
|
|
91
|
+
raise error_type(f"{field_name} must be a finite number >= {minimum}.")
|
|
92
|
+
return number
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def require_enum_member(
|
|
96
|
+
*,
|
|
97
|
+
field_name: str,
|
|
98
|
+
value: object,
|
|
99
|
+
enum_type: type[_TEnum],
|
|
100
|
+
error_type: _ErrorType = InvalidNetworkConfigurationError,
|
|
101
|
+
) -> _TEnum:
|
|
102
|
+
if not isinstance(value, enum_type):
|
|
103
|
+
raise error_type(f"{field_name} must be a {enum_type.__name__} value.")
|
|
104
|
+
return value
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Typed hook dispatch helpers for unified network event handling.
|
|
3
|
+
|
|
4
|
+
This module exposes :class:`BaseNetworkEventHandler`, a convenience base class
|
|
5
|
+
that keeps the single ``on_event(event)`` callback contract while letting
|
|
6
|
+
applications override per-event async hooks instead of writing their own
|
|
7
|
+
``isinstance`` dispatch ladder.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import cast
|
|
13
|
+
|
|
14
|
+
from aionetx.api.bytes_received_event import BytesReceivedEvent
|
|
15
|
+
from aionetx.api.component_lifecycle_changed_event import ComponentLifecycleChangedEvent
|
|
16
|
+
from aionetx.api.connection_events import (
|
|
17
|
+
ConnectionClosedEvent,
|
|
18
|
+
ConnectionOpenedEvent,
|
|
19
|
+
ConnectionRejectedEvent,
|
|
20
|
+
)
|
|
21
|
+
from aionetx.api.network_error_event import NetworkErrorEvent
|
|
22
|
+
from aionetx.api.handler_failure_policy_stop_event import HandlerFailurePolicyStopEvent
|
|
23
|
+
from aionetx.api._event_registry import EventHandlerMethod, NETWORK_EVENT_HANDLER_METHODS
|
|
24
|
+
from aionetx.api.network_event import NetworkEvent
|
|
25
|
+
from aionetx.api.network_event_handler_protocol import NetworkEventHandlerProtocol
|
|
26
|
+
from aionetx.api.reconnect_events import (
|
|
27
|
+
ReconnectAttemptFailedEvent,
|
|
28
|
+
ReconnectAttemptStartedEvent,
|
|
29
|
+
ReconnectScheduledEvent,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BaseNetworkEventHandler(NetworkEventHandlerProtocol):
|
|
34
|
+
"""
|
|
35
|
+
Convenience base class with typed no-op hooks.
|
|
36
|
+
|
|
37
|
+
`NetworkEventHandlerProtocol` stays unified as `on_event(event)`.
|
|
38
|
+
This class keeps that contract but provides optional typed hooks so users can
|
|
39
|
+
implement readable per-event handlers without writing isinstance chains.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
async def on_event(self, event: NetworkEvent) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Route one network event to the matching typed hook.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
event: Event instance emitted by a transport or component.
|
|
48
|
+
"""
|
|
49
|
+
for event_type, handler_name in NETWORK_EVENT_HANDLER_METHODS.items():
|
|
50
|
+
if isinstance(event, event_type):
|
|
51
|
+
handler = cast(EventHandlerMethod, getattr(self, handler_name))
|
|
52
|
+
await handler(event)
|
|
53
|
+
break
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
async def on_connection_opened(self, event: ConnectionOpenedEvent) -> None:
|
|
57
|
+
"""Handle ``ConnectionOpenedEvent`` with a default no-op implementation."""
|
|
58
|
+
del event
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
async def on_connection_closed(self, event: ConnectionClosedEvent) -> None:
|
|
62
|
+
"""Handle ``ConnectionClosedEvent`` with a default no-op implementation."""
|
|
63
|
+
del event
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
async def on_connection_rejected(self, event: ConnectionRejectedEvent) -> None:
|
|
67
|
+
"""Handle ``ConnectionRejectedEvent`` with a default no-op implementation."""
|
|
68
|
+
del event
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
async def on_bytes_received(self, event: BytesReceivedEvent) -> None:
|
|
72
|
+
"""Handle ``BytesReceivedEvent`` with a default no-op implementation."""
|
|
73
|
+
del event
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
async def on_error(self, event: NetworkErrorEvent) -> None:
|
|
77
|
+
"""Handle ``NetworkErrorEvent`` with a default no-op implementation."""
|
|
78
|
+
del event
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
async def on_handler_failure_policy_stop(self, event: HandlerFailurePolicyStopEvent) -> None:
|
|
82
|
+
"""Handle ``HandlerFailurePolicyStopEvent`` with a default no-op implementation."""
|
|
83
|
+
del event
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
async def on_component_lifecycle_changed(
|
|
87
|
+
self,
|
|
88
|
+
event: ComponentLifecycleChangedEvent,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Handle ``ComponentLifecycleChangedEvent`` with a default no-op implementation."""
|
|
91
|
+
del event
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
async def on_reconnect_attempt_started(
|
|
95
|
+
self,
|
|
96
|
+
event: ReconnectAttemptStartedEvent,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Handle ``ReconnectAttemptStartedEvent`` with a default no-op implementation."""
|
|
99
|
+
del event
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
async def on_reconnect_attempt_failed(
|
|
103
|
+
self,
|
|
104
|
+
event: ReconnectAttemptFailedEvent,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Handle ``ReconnectAttemptFailedEvent`` with a default no-op implementation."""
|
|
107
|
+
del event
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
async def on_reconnect_scheduled(self, event: ReconnectScheduledEvent) -> None:
|
|
111
|
+
"""Handle ``ReconnectScheduledEvent`` with a default no-op implementation."""
|
|
112
|
+
del event
|
|
113
|
+
return None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared capability protocol for bytes-like send operations.
|
|
3
|
+
|
|
4
|
+
This module defines the narrowest send contract used across stream and
|
|
5
|
+
datagram transports. Role-specific protocols layer lifecycle, addressing, and
|
|
6
|
+
connection semantics on top of this capability.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Protocol
|
|
12
|
+
|
|
13
|
+
from aionetx.api.bytes_like import BytesLike
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ByteSenderProtocol(Protocol):
|
|
17
|
+
"""
|
|
18
|
+
Capability protocol for components that can send raw bytes-like payloads.
|
|
19
|
+
|
|
20
|
+
This capability means only: "can send bytes-like payloads". It does not
|
|
21
|
+
imply shared addressing semantics (connected stream vs datagram target),
|
|
22
|
+
shared lifecycle model, or shared connection supervision behavior.
|
|
23
|
+
Use role-specific protocols when you need transport-specific guarantees.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
async def send(self, data: BytesLike) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Send raw bytes-like payload.
|
|
29
|
+
|
|
30
|
+
Implementations may differ in lifecycle, addressing, and transport
|
|
31
|
+
failure semantics. A successful return means the payload was accepted
|
|
32
|
+
by the local transport path; it does not guarantee remote delivery.
|
|
33
|
+
Role-specific protocols document their concrete exceptions.
|
|
34
|
+
"""
|
|
35
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared bytes-like payload alias used across transport APIs.
|
|
3
|
+
|
|
4
|
+
``BytesLike`` intentionally matches the payload types accepted by Python's
|
|
5
|
+
socket and stream APIs: ``bytes``, ``bytearray``, and ``memoryview``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TypeAlias
|
|
11
|
+
|
|
12
|
+
BytesLike: TypeAlias = bytes | bytearray | memoryview
|
|
13
|
+
|
|
14
|
+
__all__ = ("BytesLike",)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event type for raw transport payload delivery.
|
|
3
|
+
|
|
4
|
+
The dataclass in this module is emitted by stream, UDP, and multicast receivers
|
|
5
|
+
whenever bytes are read from the underlying transport.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class BytesReceivedEvent:
|
|
15
|
+
"""
|
|
16
|
+
Event emitted when raw bytes are read from a transport.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
resource_id: Identifier of the transport/source stream.
|
|
20
|
+
data: Raw transport payload bytes with no framing/parsing.
|
|
21
|
+
remote_host: Optional remote sender host metadata (typically present for
|
|
22
|
+
UDP/multicast datagrams, may be None for stream transports).
|
|
23
|
+
remote_port: Optional remote sender port metadata (typically present for
|
|
24
|
+
UDP/multicast datagrams, may be None for stream transports).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
resource_id: str
|
|
28
|
+
data: bytes
|
|
29
|
+
remote_host: str | None = None
|
|
30
|
+
remote_port: int | None = None
|