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.
Files changed (73) hide show
  1. aionetx/__init__.py +65 -0
  2. aionetx/api/__init__.py +137 -0
  3. aionetx/api/_event_registry.py +182 -0
  4. aionetx/api/_validation.py +104 -0
  5. aionetx/api/base_network_event_handler.py +113 -0
  6. aionetx/api/byte_sender_protocol.py +35 -0
  7. aionetx/api/bytes_like.py +14 -0
  8. aionetx/api/bytes_received_event.py +30 -0
  9. aionetx/api/component_lifecycle_changed_event.py +30 -0
  10. aionetx/api/component_lifecycle_state.py +19 -0
  11. aionetx/api/connection_events.py +71 -0
  12. aionetx/api/connection_lifecycle.py +29 -0
  13. aionetx/api/connection_metadata.py +34 -0
  14. aionetx/api/connection_protocol.py +77 -0
  15. aionetx/api/diagnostics.py +49 -0
  16. aionetx/api/error_policy.py +18 -0
  17. aionetx/api/errors.py +32 -0
  18. aionetx/api/event_delivery_settings.py +90 -0
  19. aionetx/api/events.py +41 -0
  20. aionetx/api/handler_failure_policy_stop_event.py +34 -0
  21. aionetx/api/heartbeat.py +80 -0
  22. aionetx/api/heartbeat_provider_protocol.py +28 -0
  23. aionetx/api/lifecycle.py +19 -0
  24. aionetx/api/managed_transport_protocol.py +72 -0
  25. aionetx/api/multicast_receiver_protocol.py +22 -0
  26. aionetx/api/multicast_receiver_settings.py +88 -0
  27. aionetx/api/network_error_event.py +24 -0
  28. aionetx/api/network_event.py +53 -0
  29. aionetx/api/network_event_handler_protocol.py +20 -0
  30. aionetx/api/network_factory.py +130 -0
  31. aionetx/api/policies.py +26 -0
  32. aionetx/api/protocols.py +35 -0
  33. aionetx/api/reconnect_events.py +59 -0
  34. aionetx/api/reconnect_jitter.py +17 -0
  35. aionetx/api/settings.py +25 -0
  36. aionetx/api/tcp_client.py +165 -0
  37. aionetx/api/tcp_reconnect_settings.py +74 -0
  38. aionetx/api/tcp_server.py +175 -0
  39. aionetx/api/typed_event_router.py +87 -0
  40. aionetx/api/udp.py +193 -0
  41. aionetx/factories/__init__.py +10 -0
  42. aionetx/factories/asyncio_network_factory.py +141 -0
  43. aionetx/implementations/__init__.py +18 -0
  44. aionetx/implementations/asyncio_impl/__init__.py +16 -0
  45. aionetx/implementations/asyncio_impl/_asyncio_datagram_receiver_base.py +1024 -0
  46. aionetx/implementations/asyncio_impl/_datagram_receiver_state.py +112 -0
  47. aionetx/implementations/asyncio_impl/_event_dispatcher_policy.py +56 -0
  48. aionetx/implementations/asyncio_impl/_event_dispatcher_queue.py +43 -0
  49. aionetx/implementations/asyncio_impl/_tcp_client_connect.py +182 -0
  50. aionetx/implementations/asyncio_impl/_tcp_client_runtime.py +119 -0
  51. aionetx/implementations/asyncio_impl/_tcp_client_stop_state.py +55 -0
  52. aionetx/implementations/asyncio_impl/_tcp_connection_helpers.py +129 -0
  53. aionetx/implementations/asyncio_impl/_tcp_server_helpers.py +365 -0
  54. aionetx/implementations/asyncio_impl/_tcp_server_stop_state.py +56 -0
  55. aionetx/implementations/asyncio_impl/asyncio_heartbeat_sender.py +187 -0
  56. aionetx/implementations/asyncio_impl/asyncio_multicast_receiver.py +158 -0
  57. aionetx/implementations/asyncio_impl/asyncio_tcp_client.py +809 -0
  58. aionetx/implementations/asyncio_impl/asyncio_tcp_connection.py +714 -0
  59. aionetx/implementations/asyncio_impl/asyncio_tcp_server.py +803 -0
  60. aionetx/implementations/asyncio_impl/asyncio_udp_receiver.py +124 -0
  61. aionetx/implementations/asyncio_impl/asyncio_udp_sender.py +240 -0
  62. aionetx/implementations/asyncio_impl/event_dispatcher.py +829 -0
  63. aionetx/implementations/asyncio_impl/identifier_utils.py +57 -0
  64. aionetx/implementations/asyncio_impl/lifecycle_internal.py +157 -0
  65. aionetx/implementations/asyncio_impl/runtime_utils.py +280 -0
  66. aionetx/implementations/asyncio_impl/tcp_client_supervision.py +395 -0
  67. aionetx/py.typed +0 -0
  68. aionetx/testing/__init__.py +115 -0
  69. aionetx-0.1.0.dist-info/METADATA +952 -0
  70. aionetx-0.1.0.dist-info/RECORD +73 -0
  71. aionetx-0.1.0.dist-info/WHEEL +5 -0
  72. aionetx-0.1.0.dist-info/licenses/LICENSE +21 -0
  73. 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__)
@@ -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