attp-sdk 0.1.0__tar.gz

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 (62) hide show
  1. attp_sdk-0.1.0/PKG-INFO +61 -0
  2. attp_sdk-0.1.0/README.md +45 -0
  3. attp_sdk-0.1.0/pyproject.toml +26 -0
  4. attp_sdk-0.1.0/src/attp/__init__.py +8 -0
  5. attp_sdk-0.1.0/src/attp/client/__init__.py +6 -0
  6. attp_sdk-0.1.0/src/attp/client/authenticator.py +84 -0
  7. attp_sdk-0.1.0/src/attp/client/configs.py +32 -0
  8. attp_sdk-0.1.0/src/attp/client/service_discovery.py +107 -0
  9. attp_sdk-0.1.0/src/attp/client/session_driver.py +120 -0
  10. attp_sdk-0.1.0/src/attp/decorators/__init__.py +6 -0
  11. attp_sdk-0.1.0/src/attp/decorators/attp_call.py +15 -0
  12. attp_sdk-0.1.0/src/attp/decorators/attp_event.py +20 -0
  13. attp_sdk-0.1.0/src/attp/decorators/attp_handler.py +15 -0
  14. attp_sdk-0.1.0/src/attp/decorators/attp_lifecycle.py +19 -0
  15. attp_sdk-0.1.0/src/attp/loadbalancer/__init__.py +7 -0
  16. attp_sdk-0.1.0/src/attp/loadbalancer/abc/cacher.py +23 -0
  17. attp_sdk-0.1.0/src/attp/loadbalancer/abc/candidate.py +7 -0
  18. attp_sdk-0.1.0/src/attp/loadbalancer/abc/strategy.py +22 -0
  19. attp_sdk-0.1.0/src/attp/loadbalancer/balancer.py +57 -0
  20. attp_sdk-0.1.0/src/attp/loadbalancer/caches/__init__.py +0 -0
  21. attp_sdk-0.1.0/src/attp/loadbalancer/caches/memory_cache.py +43 -0
  22. attp_sdk-0.1.0/src/attp/loadbalancer/configs.py +7 -0
  23. attp_sdk-0.1.0/src/attp/loadbalancer/evaluator.py +30 -0
  24. attp_sdk-0.1.0/src/attp/loadbalancer/strategies/__init__.py +0 -0
  25. attp_sdk-0.1.0/src/attp/loadbalancer/strategies/round_robin.py +29 -0
  26. attp_sdk-0.1.0/src/attp/providers.py +322 -0
  27. attp_sdk-0.1.0/src/attp/server/__init__.py +6 -0
  28. attp_sdk-0.1.0/src/attp/server/abc/auth_strategy.py +13 -0
  29. attp_sdk-0.1.0/src/attp/server/attp_server.py +103 -0
  30. attp_sdk-0.1.0/src/attp/server/auth_hmac.py +162 -0
  31. attp_sdk-0.1.0/src/attp/server/configs.py +15 -0
  32. attp_sdk-0.1.0/src/attp/server/session_driver.py +143 -0
  33. attp_sdk-0.1.0/src/attp/shared/lifecycle_service.py +16 -0
  34. attp_sdk-0.1.0/src/attp/shared/limits.py +10 -0
  35. attp_sdk-0.1.0/src/attp/shared/multireceiver.py +87 -0
  36. attp_sdk-0.1.0/src/attp/shared/namespaces/dispatcher.py +46 -0
  37. attp_sdk-0.1.0/src/attp/shared/namespaces/router.py +115 -0
  38. attp_sdk-0.1.0/src/attp/shared/objects/dispatcher.py +106 -0
  39. attp_sdk-0.1.0/src/attp/shared/objects/eventbus.py +114 -0
  40. attp_sdk-0.1.0/src/attp/shared/objects/stream.py +27 -0
  41. attp_sdk-0.1.0/src/attp/shared/receiver.py +19 -0
  42. attp_sdk-0.1.0/src/attp/shared/secrets.py +73 -0
  43. attp_sdk-0.1.0/src/attp/shared/sessions/additional_mixins.py +140 -0
  44. attp_sdk-0.1.0/src/attp/shared/sessions/driver.py +197 -0
  45. attp_sdk-0.1.0/src/attp/shared/transmitter.py +250 -0
  46. attp_sdk-0.1.0/src/attp/shared/utils/ack_gate.py +87 -0
  47. attp_sdk-0.1.0/src/attp/shared/utils/callbacks.py +94 -0
  48. attp_sdk-0.1.0/src/attp/shared/utils/executor.py +130 -0
  49. attp_sdk-0.1.0/src/attp/shared/utils/qsequence.py +219 -0
  50. attp_sdk-0.1.0/src/attp/shared/utils/stream_receiver.py +39 -0
  51. attp_sdk-0.1.0/src/attp/types/context.py +26 -0
  52. attp_sdk-0.1.0/src/attp/types/exceptions/attp_exception.py +59 -0
  53. attp_sdk-0.1.0/src/attp/types/exceptions/load_balancer.py +14 -0
  54. attp_sdk-0.1.0/src/attp/types/exceptions/protocol_error.py +15 -0
  55. attp_sdk-0.1.0/src/attp/types/frame.py +68 -0
  56. attp_sdk-0.1.0/src/attp/types/frames/accepted.py +8 -0
  57. attp_sdk-0.1.0/src/attp/types/frames/auth.py +8 -0
  58. attp_sdk-0.1.0/src/attp/types/frames/error.py +19 -0
  59. attp_sdk-0.1.0/src/attp/types/frames/ready.py +13 -0
  60. attp_sdk-0.1.0/src/attp/types/frames/route_mapping.py +13 -0
  61. attp_sdk-0.1.0/src/attp/types/routes.py +25 -0
  62. attp_sdk-0.1.0/src/attp/types/streaming_signature.py +6 -0
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: attp-sdk
3
+ Version: 0.1.0
4
+ Summary: attp-sdk
5
+ Author: Zahcoder34
6
+ Author-email: Zakhriw@gmail.com
7
+ Requires-Python: >=3.11,<3.14
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Dist: ascender-framework (>=2.0.4,<3.0.0)
13
+ Requires-Dist: attp-core (>=0.2.4,<0.3.0)
14
+ Requires-Dist: msgpack (>=1.1.2,<2.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # Ascender Framework Project
18
+
19
+ ## Overview
20
+
21
+ This project is built using the **Ascender Framework**, a structured FastAPI-based framework designed for modular and scalable applications.
22
+
23
+ ---
24
+
25
+ ## Getting Started
26
+
27
+ ### Prerequisites
28
+
29
+ Ensure you have the following dependencies installed:
30
+
31
+ - **Python 3.11 or higher**
32
+ - **Poetry** (for dependency management)
33
+ - **pip** (Python's package manager)
34
+
35
+ ### Installation
36
+
37
+ 1. **Install Poetry** (if not already installed):
38
+
39
+ ```bash
40
+ pip install poetry
41
+ ```
42
+
43
+ 2. **Configure Poetry to use virtual environments**:
44
+
45
+ ```bash
46
+ poetry config virtualenvs.create true
47
+ ```
48
+
49
+ 3. **Install project dependencies**:
50
+
51
+ ```bash
52
+ poetry install
53
+ ```
54
+
55
+ 4. **Run development server**:
56
+
57
+ ```bash
58
+ ascender run serve
59
+ ```
60
+
61
+ If everything works, you're done!
@@ -0,0 +1,45 @@
1
+ # Ascender Framework Project
2
+
3
+ ## Overview
4
+
5
+ This project is built using the **Ascender Framework**, a structured FastAPI-based framework designed for modular and scalable applications.
6
+
7
+ ---
8
+
9
+ ## Getting Started
10
+
11
+ ### Prerequisites
12
+
13
+ Ensure you have the following dependencies installed:
14
+
15
+ - **Python 3.11 or higher**
16
+ - **Poetry** (for dependency management)
17
+ - **pip** (Python's package manager)
18
+
19
+ ### Installation
20
+
21
+ 1. **Install Poetry** (if not already installed):
22
+
23
+ ```bash
24
+ pip install poetry
25
+ ```
26
+
27
+ 2. **Configure Poetry to use virtual environments**:
28
+
29
+ ```bash
30
+ poetry config virtualenvs.create true
31
+ ```
32
+
33
+ 3. **Install project dependencies**:
34
+
35
+ ```bash
36
+ poetry install
37
+ ```
38
+
39
+ 4. **Run development server**:
40
+
41
+ ```bash
42
+ ascender run serve
43
+ ```
44
+
45
+ If everything works, you're done!
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "attp-sdk"
3
+ version = "0.1.0"
4
+ description = "attp-sdk"
5
+ authors = [
6
+ {name = "Zahcoder34",email = "Zakhriw@gmail.com"},
7
+ {name = "AscenderTeam",email = "team@ascender.space"},
8
+ ]
9
+ readme = "README.md"
10
+ requires-python = ">=3.11,<3.14"
11
+ dependencies = [
12
+ "ascender-framework (>=2.0.4,<3.0.0)",
13
+ "attp-core (>=0.2.4,<0.3.0)",
14
+ "msgpack (>=1.1.2,<2.0.0)",
15
+ ]
16
+
17
+ [tool.poetry]
18
+ package-mode = true
19
+ packages = [
20
+ { include = "attp", from = "src" }
21
+ ]
22
+
23
+
24
+ [build-system]
25
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
26
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,8 @@
1
+ from attp.decorators import AttpCall, AttpErrorHandler, AttpEvent, AttpLifecycle
2
+ from .providers import provideAttp
3
+ from .types.frame import AttpFrameDTO
4
+ from .types.context import AttpContext
5
+ from .shared.objects.stream import StreamObject
6
+
7
+
8
+ __all__ = ["provideAttp", "AttpFrameDTO", "AttpContext", "StreamObject", "AttpCall", "AttpEvent", "AttpLifecycle", "AttpErrorHandler"]
@@ -0,0 +1,6 @@
1
+ from .authenticator import ConnectionAuthenticator
2
+ from .configs import ServiceDiscoveryConfigs, AttpClientConfigs
3
+ from .service_discovery import ServiceDiscovery
4
+ from .session_driver import ClientSessionDriver
5
+
6
+ __all__ = ["ConnectionAuthenticator", "ServiceDiscoveryConfigs", "AttpClientConfigs", "ServiceDiscovery", "ClientSessionDriver"]
@@ -0,0 +1,84 @@
1
+ import hashlib
2
+ import hmac
3
+ import os
4
+ import secrets
5
+ import time
6
+ from typing import Any, Mapping
7
+
8
+ from attp.shared.secrets import resolve_secret_if_ref, parse_secret_ref, SecretRef
9
+
10
+
11
+ class ConnectionAuthenticator:
12
+ def __init__(
13
+ self,
14
+ conn_uri: str,
15
+ namespace: str,
16
+ authorization: Any | None = None,
17
+ data: Any | None = None
18
+ ) -> None:
19
+ self.authorization = authorization
20
+ self.conn_uri = conn_uri
21
+ self.namespace = namespace
22
+ self.data = data
23
+ self.AUTH_TIMEOUT = float(os.getenv("ATTP_CLIENT_CONNECTION_TIMEOUT", 10))
24
+
25
+ async def authenticate(self):
26
+ """
27
+ Method is invoked when authentication process starts.
28
+ Usually server requires protocol authentication, and the connection authenticator signs the
29
+ """
30
+ return resolve_secret_if_ref(self.authorization)
31
+
32
+ async def send_hello(self):
33
+ """
34
+ Method is invoked only after authentication, to send the `data` given on `__init__` to the remote peer.
35
+ This method is mandatory for handshake part, for future authentications, it
36
+ """
37
+ return self.data
38
+
39
+
40
+ class HmacConnectionAuthenticator(ConnectionAuthenticator):
41
+ def __init__(
42
+ self,
43
+ conn_uri: str,
44
+ namespace: str,
45
+ *,
46
+ secret: str | SecretRef | Mapping[str, Any],
47
+ node_id: str | None = None,
48
+ key_id: str | None = None,
49
+ ttl_seconds: int = 30,
50
+ max_clock_skew: int = 5,
51
+ ) -> None:
52
+ super().__init__(conn_uri, namespace, authorization=None, data=None)
53
+ self._secret_ref = parse_secret_ref(secret)
54
+ if self._secret_ref is None:
55
+ raise TypeError("secret must be a string or secret reference.")
56
+
57
+ self.node_id = node_id or os.getenv("ATTP_NODE_ID") or "node"
58
+ self.key_id = key_id
59
+ self.ttl_seconds = ttl_seconds
60
+ self.max_clock_skew = max_clock_skew
61
+ self.AUTH_TIMEOUT = float(
62
+ os.getenv("ATTP_CLIENT_CONNECTION_TIMEOUT", ttl_seconds + max_clock_skew + 5)
63
+ )
64
+
65
+ def _sign(self, ts: int, nonce: str) -> str:
66
+ secret = self._secret_ref.resolve() # type: ignore
67
+ message = f"{self.namespace}:{self.node_id}:{ts}:{nonce}".encode("utf-8")
68
+ return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
69
+
70
+ async def authenticate(self):
71
+ ts = int(time.time())
72
+ nonce = secrets.token_hex(16)
73
+ sig = self._sign(ts, nonce)
74
+
75
+ payload = {
76
+ "alg": "HS256",
77
+ "ts": ts,
78
+ "nonce": nonce,
79
+ "sig": sig,
80
+ "node_id": self.node_id,
81
+ }
82
+ if self.key_id:
83
+ payload["kid"] = self.key_id
84
+ return payload
@@ -0,0 +1,32 @@
1
+ from typing import Any, Callable
2
+ from ascender.common import BaseDTO
3
+ from pydantic import Field
4
+
5
+ from attp.client.authenticator import ConnectionAuthenticator
6
+ from attp.shared.limits import AttpLimits
7
+
8
+
9
+ class AttpClientConfigs(BaseDTO):
10
+ model_config = {"arbitrary_types_allowed": True}
11
+
12
+ remote_uri: str # attp://host:port
13
+ namespace: str = "default"
14
+ data: Any | None = None
15
+ authorization: Any | None = None
16
+ auth: Any | None = None
17
+ capabilities: list[str] = Field(default_factory=lambda: ["schema/msgpack", "streaming"])
18
+
19
+
20
+ class ServiceDiscoveryConfigs(BaseDTO):
21
+ model_config = {"arbitrary_types_allowed": True}
22
+
23
+ peers: list[str | AttpClientConfigs]
24
+ limits: AttpLimits
25
+ authenticator: (
26
+ ConnectionAuthenticator
27
+ | type[ConnectionAuthenticator]
28
+ | Callable[[AttpClientConfigs], ConnectionAuthenticator]
29
+ | None
30
+ ) = None
31
+ reconnection: bool = True
32
+ max_retries: int = 20
@@ -0,0 +1,107 @@
1
+ import asyncio
2
+ from logging import Logger
3
+ from typing import Annotated
4
+
5
+ from ascender.core import Inject
6
+ from attp.client.authenticator import ConnectionAuthenticator
7
+ from attp.client.configs import AttpClientConfigs, ServiceDiscoveryConfigs
8
+ from attp.client.session_driver import ClientSessionDriver
9
+ from attp.server.session_driver import ServerSessionDriver
10
+ from attp.shared.lifecycle_service import LifecycleService
11
+
12
+ from attp_core.rs_api import AttpClientSession, PyAttpMessage
13
+
14
+ from attp.shared.multireceiver import AttpMultiReceiver
15
+ from attp.shared.namespaces.dispatcher import NamespaceDispatcher
16
+ from attp.shared.objects.dispatcher import AttpFrameDispatcher
17
+ from attp.shared.sessions.driver import AttpSessionDriver
18
+
19
+
20
+ class ServiceDiscovery(LifecycleService):
21
+ def __init__(
22
+ self,
23
+ configs: ServiceDiscoveryConfigs,
24
+ namespaces: NamespaceDispatcher,
25
+ dispatcher: AttpFrameDispatcher,
26
+ logger: Annotated[Logger, Inject("ASC_LOGGER")]
27
+ ) -> None:
28
+ super().__init__()
29
+ self.configs = configs
30
+
31
+ self.multireceiver = AttpMultiReceiver[tuple[AttpSessionDriver, PyAttpMessage]](lambda d: d[0].namespace, fanout_global=True, auto_create=True)
32
+ self.namespaces = namespaces
33
+ self.dispatcher = dispatcher
34
+
35
+ self.conlock = asyncio.Lock()
36
+
37
+ self.logger = logger
38
+ self.is_active = False
39
+
40
+ def activate(self):
41
+ if not self.is_active:
42
+ self.logger.info("Activating service discovery...")
43
+ self.is_active = True
44
+
45
+ async def start_initial_connections(self):
46
+ for peer in self.configs.peers:
47
+ if isinstance(peer, str):
48
+ peer = AttpClientConfigs(remote_uri=peer)
49
+
50
+ authenticator = self.configs.authenticator
51
+ if isinstance(authenticator, type) and issubclass(authenticator, ConnectionAuthenticator):
52
+ authenticator = authenticator(peer.remote_uri, peer.namespace)
53
+ elif callable(authenticator):
54
+ authenticator = authenticator(peer)
55
+ try:
56
+ await self.initiate_connection(peer, conn_authenticator=authenticator)
57
+ except Exception as exc:
58
+ self.logger.error(
59
+ "[cyan]ATTP[/] ┆ Failed to connect to peer %s (%s)",
60
+ peer.remote_uri,
61
+ exc,
62
+ )
63
+
64
+ async def on_startup(self):
65
+ asyncio.create_task(self.start_initial_connections())
66
+
67
+ async def on_shutdown(self):
68
+ self.dispatcher.stop_all()
69
+
70
+ async def on_session_termination(self, session_driver: ClientSessionDriver):
71
+ try:
72
+ self.namespaces.remove_session(session_driver.namespace, session_driver)
73
+ self.logger.info("[cyan]ATTP[/] ┆ Session (%s) disconnected.", session_driver.session_id or "unknown")
74
+
75
+ except ValueError:
76
+ self.logger.info("[cyan]ATTP[/] ┆ Session (%s) disconnected before being registered", session_driver._session or "unknown")
77
+
78
+
79
+ async def initiate_connection(self, config: AttpClientConfigs, conn_authenticator: ConnectionAuthenticator | None = None):
80
+ client = AttpClientSession(config.remote_uri, limits=self.configs.limits.to_model())
81
+
82
+ client = await client.connect(max_retries=self.configs.max_retries)
83
+
84
+ if not client.session:
85
+ raise ConnectionError("Failed to connect to the server.")
86
+
87
+ driver = ClientSessionDriver(client.session, on_termination=self.on_session_termination)
88
+
89
+ if not conn_authenticator:
90
+ authenticator = ConnectionAuthenticator(config.remote_uri, config.namespace, config.authorization, config.data)
91
+ else:
92
+ authenticator = conn_authenticator
93
+
94
+ try:
95
+ namespace, _ = await driver.start(config.capabilities, authenticator)
96
+ except TimeoutError:
97
+ self.logger.error("[cyan]ATTP[/] ┆ Session (%s) %s failed to authenticate, flushing the connection!", client.session.peername, client.session.session_id)
98
+ await driver.close()
99
+ del driver
100
+ return
101
+
102
+ async with self.conlock:
103
+ self.namespaces.add_session(namespace, driver)
104
+ receiver = self.multireceiver.receiver(namespace)
105
+ self.dispatcher.start(receiver)
106
+
107
+ await driver.listen(receiver)
@@ -0,0 +1,120 @@
1
+ import asyncio
2
+ import os
3
+ import traceback
4
+ from typing import Annotated
5
+ from ascender.core import inject
6
+ from typing_extensions import Doc
7
+ from attp.client.authenticator import ConnectionAuthenticator
8
+ from attp.shared.namespaces.router import AttpRouter
9
+ from attp.shared.sessions.additional_mixins import EnhancedFrameTransmitterMixin, StreamingFrameTransmitterMixin
10
+ from attp.shared.sessions.driver import SessionTerminatorMixin
11
+
12
+ from attp_core.rs_api import PyAttpMessage, AttpCommand
13
+
14
+ from attp.types.exceptions.attp_exception import AttpException
15
+ from attp.types.exceptions.protocol_error import ProtocolError
16
+ from attp.types.frames.accepted import IAcceptedDTO
17
+ from attp.types.frames.auth import IAuthDTO
18
+ from attp.types.frames.ready import IReadyDTO
19
+
20
+
21
+ class ClientSessionDriver(
22
+ SessionTerminatorMixin,
23
+ EnhancedFrameTransmitterMixin,
24
+ StreamingFrameTransmitterMixin
25
+ ):
26
+ connection_estabilished_at: Annotated[str | None, Doc("Last connection time according to server's timezone in an ISO-format.")]
27
+ authenticator: ConnectionAuthenticator
28
+ router: AttpRouter = inject(AttpRouter)
29
+
30
+ async def start(self, capabilities: list, conn_authenticator: ConnectionAuthenticator):
31
+ asyncio.create_task(self.start_listener())
32
+ self._role = "client"
33
+ self._capabilities = capabilities
34
+ self._namespace = conn_authenticator.namespace
35
+ if os.getenv("ATTP_AUTH_DEBUG") == "1":
36
+ log = getattr(self.logger, "info", None)
37
+ if callable(log):
38
+ log(
39
+ "[cyan]ATTP[/] ┆ Sending AUTH namespace=%s",
40
+ conn_authenticator.namespace,
41
+ )
42
+ await self.send_frame(
43
+ PyAttpMessage(
44
+ route_id=1,
45
+ command_type=AttpCommand.AUTH,
46
+ correlation_id=None,
47
+ payload=IAuthDTO(
48
+ namespace=conn_authenticator.namespace,
49
+ data=await conn_authenticator.authenticate()
50
+ ).mpd(),
51
+ version=self.version_bytes()
52
+ )
53
+ )
54
+ self.authenticator = conn_authenticator
55
+ try:
56
+ await asyncio.wait_for(self.auth_flag.wait(), timeout=conn_authenticator.AUTH_TIMEOUT)
57
+ except asyncio.TimeoutError:
58
+ raise TimeoutError("Authentication timed out for session {}".format(self.session_id))
59
+
60
+ return self.namespace, self.session_id
61
+
62
+ async def _on_event(self, events: list[PyAttpMessage]):
63
+ self.logger.debug(f"[cyan]ATTP[/] ┆ Received a new message from session {self.session_id}")
64
+
65
+ for event in events:
66
+ if event.route_id == 0 and event.command_type == AttpCommand.READY:
67
+ try:
68
+ if not event.payload:
69
+ continue
70
+ if os.getenv("ATTP_AUTH_DEBUG") == "1":
71
+ log = getattr(self.logger, "info", None)
72
+ if callable(log):
73
+ log("[cyan]ATTP[/] ┆ READY received")
74
+ self._enqueue_incoming(event)
75
+ await self._register_connection(IAcceptedDTO.mps(event.payload))
76
+ except Exception as e:
77
+ traceback.print_exc()
78
+ # TODO: Implement error reporting
79
+ await self.handle_disconnect()
80
+ return
81
+
82
+ elif event.command_type == AttpCommand.DISCONNECT:
83
+ await self.handle_disconnect()
84
+ return
85
+ elif event.command_type == AttpCommand.ERR:
86
+ self._enqueue_incoming(event)
87
+ return
88
+ else:
89
+ if self.is_authenticated:
90
+ self._enqueue_incoming(event)
91
+
92
+ async def _register_connection(self, frame: IAcceptedDTO):
93
+ super()._register_connection(frame)
94
+ try:
95
+ print("RRR", frame.routes)
96
+ self.router.include_remote_routes(self.namespace, frame.routes, "client")
97
+ except ProtocolError as e:
98
+ await self.send_error(0, exception=AttpException(400, message=str(e), detail=traceback.format_exc(), fatal=True))
99
+ await self.close()
100
+ return
101
+
102
+ self.auth_flag.set()
103
+ self.connection_estabilished_at = frame.server_time
104
+
105
+ routes = self.router.get_routes(namespace=self.namespace)
106
+ print("CLIENT REGISTERING ROUTES:", routes)
107
+
108
+ await self.send_frame(
109
+ PyAttpMessage(
110
+ route_id=0,
111
+ command_type=AttpCommand.READY,
112
+ correlation_id=None,
113
+ payload=IReadyDTO(
114
+ data=self.authenticator.data,
115
+ caps=self.capabilities,
116
+ routes=routes
117
+ ).mpd(),
118
+ version=self.version_bytes()
119
+ )
120
+ )
@@ -0,0 +1,6 @@
1
+ from .attp_call import AttpCall
2
+ from .attp_event import AttpEvent
3
+ from .attp_handler import AttpErrorHandler
4
+ from .attp_lifecycle import AttpLifecycle
5
+
6
+ __all__ = ["AttpCall", "AttpEvent", "AttpErrorHandler", "AttpLifecycle"]
@@ -0,0 +1,15 @@
1
+ from typing import Any, Callable
2
+ from ascender.core import ControllerDecoratorHook, inject
3
+
4
+ from attp.shared.namespaces.router import AttpRouter
5
+
6
+
7
+ class AttpCall(ControllerDecoratorHook):
8
+ router: AttpRouter = inject(AttpRouter)
9
+
10
+ def __init__(self, pattern: str, namespace: str = "default") -> None:
11
+ self.pattern = pattern
12
+ self.namespace = namespace
13
+
14
+ def on_load(self, callable: Callable[..., Any]):
15
+ self.router.add_route("message", self.pattern, callable, namespace=self.namespace)
@@ -0,0 +1,20 @@
1
+ from typing import Any, Callable
2
+ from ascender.core import ControllerDecoratorHook, inject
3
+
4
+ from attp.shared.namespaces.router import AttpRouter
5
+
6
+
7
+ class AttpEvent(ControllerDecoratorHook):
8
+
9
+ router: AttpRouter = inject(AttpRouter)
10
+
11
+ def __init__(
12
+ self,
13
+ pattern: str,
14
+ namespace: str = "default"
15
+ ) -> None:
16
+ self.pattern = pattern
17
+ self.namespace = namespace
18
+
19
+ def on_load(self, callable: Callable[..., Any]):
20
+ self.router.add_event(self.pattern, callable, namespace=self.namespace)
@@ -0,0 +1,15 @@
1
+ from typing import Any, Callable
2
+ from ascender.core import ControllerDecoratorHook, inject
3
+
4
+ from attp.shared.namespaces.router import AttpRouter
5
+
6
+
7
+ class AttpErrorHandler(ControllerDecoratorHook):
8
+ router: AttpRouter = inject(AttpRouter)
9
+
10
+ def __init__(self, pattern: str, namespace: str = "default") -> None:
11
+ self.pattern = pattern
12
+ self.namespace = namespace
13
+
14
+ def on_load(self, callable: Callable[..., Any]):
15
+ self.router.add_error_handler(self.pattern, callable, namespace=self.namespace)
@@ -0,0 +1,19 @@
1
+ from typing import Any, Callable, Literal
2
+ from ascender.core import ControllerDecoratorHook, inject
3
+
4
+ from attp.client.service_discovery import ServiceDiscovery
5
+ from attp.server.attp_server import AttpServer
6
+ from attp.shared.namespaces.router import AttpRouter
7
+
8
+
9
+ class AttpLifecycle(ControllerDecoratorHook):
10
+ router: AttpRouter = inject(AttpRouter)
11
+
12
+ def __init__(self, event: Literal["connect", "disconnect"], namespace: str = "default") -> None:
13
+ self.event = event
14
+ self.namespace = namespace
15
+
16
+ def on_load(self, callable: Callable[..., Any]):
17
+ inject(AttpServer).activate()
18
+ inject(ServiceDiscovery).activate()
19
+ self.router.add_route(self.event, self.event, callable, namespace=self.namespace) # type: ignore
@@ -0,0 +1,7 @@
1
+ from .balancer import AttpLoadBalancer
2
+ from .configs import BalancerConfigs
3
+ from .abc.cacher import StrategyCacher
4
+ from .abc.candidate import Candidate
5
+ from .abc.strategy import BalancingStrategy
6
+
7
+ __all__ = ["AttpLoadBalancer", "BalancerConfigs", "StrategyCacher", "Candidate", "BalancingStrategy"]
@@ -0,0 +1,23 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, TypeVar
3
+
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class StrategyCacher(ABC):
9
+
10
+ @abstractmethod
11
+ async def store(self, key: str, value: Any) -> Any:
12
+ ...
13
+
14
+ @abstractmethod
15
+ async def get(self, key: str, *, expected_type: type[T] | None = None) -> T | Any:
16
+ ...
17
+
18
+ @abstractmethod
19
+ async def increment(self, key: str, *, delta: int = 1, initial: int = 0) -> int:
20
+ ...
21
+
22
+ async def keys(self):
23
+ raise NotImplementedError
@@ -0,0 +1,7 @@
1
+ from typing import TypeAlias
2
+
3
+ from attp.server.session_driver import ServerSessionDriver
4
+ from attp.shared.sessions.driver import AttpSessionDriver
5
+
6
+
7
+ Candidate: TypeAlias = AttpSessionDriver | ServerSessionDriver
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from attp.loadbalancer.abc.cacher import StrategyCacher
5
+ from attp.loadbalancer.abc.candidate import Candidate
6
+ from attp.shared.utils.qsequence import QSequence
7
+
8
+
9
+ class BalancingStrategy(ABC):
10
+ name: str
11
+
12
+ @abstractmethod
13
+ def __init__(
14
+ self,
15
+ configs: Any,
16
+ cacher: StrategyCacher,
17
+ ) -> None:
18
+ ...
19
+
20
+ @abstractmethod
21
+ async def balance(self, default: Candidate, candidates: QSequence[Candidate]) -> Candidate:
22
+ pass