durable-actors 0.5.5__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 (46) hide show
  1. durable_actors-0.5.5/.gitignore +22 -0
  2. durable_actors-0.5.5/LICENSE.md +9 -0
  3. durable_actors-0.5.5/PKG-INFO +111 -0
  4. durable_actors-0.5.5/README.md +90 -0
  5. durable_actors-0.5.5/pyproject.toml +44 -0
  6. durable_actors-0.5.5/src/durable_actors/__init__.py +25 -0
  7. durable_actors-0.5.5/src/durable_actors/actor.py +145 -0
  8. durable_actors-0.5.5/src/durable_actors/build.py +237 -0
  9. durable_actors-0.5.5/src/durable_actors/client.py +512 -0
  10. durable_actors-0.5.5/src/durable_actors/codegen.py +781 -0
  11. durable_actors-0.5.5/src/durable_actors/connection.py +158 -0
  12. durable_actors-0.5.5/src/durable_actors/contract.py +390 -0
  13. durable_actors-0.5.5/src/durable_actors/executor_wire.py +41 -0
  14. durable_actors-0.5.5/src/durable_actors/fields.py +61 -0
  15. durable_actors-0.5.5/src/durable_actors/generated.py +44 -0
  16. durable_actors-0.5.5/src/durable_actors/guards.py +20 -0
  17. durable_actors-0.5.5/src/durable_actors/host.py +202 -0
  18. durable_actors-0.5.5/src/durable_actors/json.py +14 -0
  19. durable_actors-0.5.5/src/durable_actors/proxy.py +45 -0
  20. durable_actors-0.5.5/src/durable_actors/py.typed +0 -0
  21. durable_actors-0.5.5/src/durable_actors/reference.py +89 -0
  22. durable_actors-0.5.5/src/durable_actors/runtime.py +282 -0
  23. durable_actors-0.5.5/src/durable_actors/sandbox.py +87 -0
  24. durable_actors-0.5.5/src/durable_actors/session.py +203 -0
  25. durable_actors-0.5.5/src/durable_actors/socket.py +287 -0
  26. durable_actors-0.5.5/src/durable_actors/subscription.py +142 -0
  27. durable_actors-0.5.5/src/durable_actors/supervisor.py +233 -0
  28. durable_actors-0.5.5/tests/fixtures/documented.py +33 -0
  29. durable_actors-0.5.5/tests/fixtures/effects.py +14 -0
  30. durable_actors-0.5.5/tests/fixtures/field_types.py +37 -0
  31. durable_actors-0.5.5/tests/fixtures/image_smoke.py +71 -0
  32. durable_actors-0.5.5/tests/fixtures/types.py +46 -0
  33. durable_actors-0.5.5/tests/test_authoring.py +261 -0
  34. durable_actors-0.5.5/tests/test_build.py +189 -0
  35. durable_actors-0.5.5/tests/test_client.py +81 -0
  36. durable_actors-0.5.5/tests/test_codegen.py +559 -0
  37. durable_actors-0.5.5/tests/test_connection.py +50 -0
  38. durable_actors-0.5.5/tests/test_host.py +239 -0
  39. durable_actors-0.5.5/tests/test_integration.py +268 -0
  40. durable_actors-0.5.5/tests/test_parity.py +280 -0
  41. durable_actors-0.5.5/tests/test_runtime.py +314 -0
  42. durable_actors-0.5.5/tests/test_sandbox.py +98 -0
  43. durable_actors-0.5.5/tests/test_session.py +161 -0
  44. durable_actors-0.5.5/tests/test_socket.py +32 -0
  45. durable_actors-0.5.5/tests/test_subscription.py +161 -0
  46. durable_actors-0.5.5/uv.lock +1215 -0
@@ -0,0 +1,22 @@
1
+ node_modules/
2
+ sdk/node_modules/
3
+ sdk/dist/
4
+ sdk/.test-dist/
5
+ packages/*/dist/
6
+ sdk/src/generated/
7
+ target/
8
+ dist-runtime/
9
+ .durable-actors/
10
+ .artifacts/
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+ .DS_Store
15
+ Thumbs.db
16
+
17
+ .venv/
18
+ __pycache__/
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ sdk-python/dist/
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Terse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.5
2
+ Name: durable-actors
3
+ Version: 0.5.5
4
+ Summary: Typed Python durable actors and generated clients
5
+ Project-URL: Repository, https://github.com/TerseAI/durable-actors
6
+ Project-URL: Documentation, https://github.com/TerseAI/durable-actors/blob/main/docs/reference/python.md
7
+ License-Expression: MIT
8
+ License-File: LICENSE.md
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: httpx<1,>=0.28
11
+ Requires-Dist: jsonschema<5,>=4.23
12
+ Requires-Dist: packaging<27,>=24
13
+ Requires-Dist: pydantic<3,>=2.12
14
+ Requires-Dist: typing-extensions<5,>=4.12
15
+ Requires-Dist: websockets<17,>=15
16
+ Provides-Extra: codegen
17
+ Requires-Dist: datamodel-code-generator<0.84,>=0.83; extra == 'codegen'
18
+ Requires-Dist: mypy<2,>=1.15; extra == 'codegen'
19
+ Requires-Dist: pip<27,>=25; extra == 'codegen'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Little Actors for Python
23
+
24
+ Define durable actors in Python and generate typed Python clients. Python 3.11+ is supported. Actor state, placement, routing, and WebSocket delivery use the same Rust runtime as the TypeScript SDK.
25
+
26
+ ```sh
27
+ pnpm dlx durable-actors init my-actors --template python
28
+ cd my-actors
29
+ pnpm install
30
+ uv sync
31
+ pnpm exec durable-actors dev
32
+ ```
33
+
34
+ The shared TypeScript CLI manages development, generation, and Python type checking. It selects the project's `.venv` (or the active environment); `DURABLE_ACTORS_PYTHON` overrides the interpreter. Install Node.js 22+, Python 3.11+, and uv. The CLI downloads the matching native runtime on macOS and Linux. When developing this repository, set `DURABLE_ACTORS_BINARY` to an absolute path to your `cargo build --locked` executable.
35
+
36
+ ## Define actors
37
+
38
+ ```python
39
+ from pydantic import BaseModel
40
+ from durable_actors import Actor, emitted, ephemeral
41
+
42
+ class Message(BaseModel):
43
+ text: str
44
+
45
+ class Chat(Actor):
46
+ count: int = 0
47
+ messages: list[Message] = emitted(default_factory=list)
48
+ busy: bool = ephemeral(False)
49
+
50
+ def append(self, message: Message) -> list[Message]:
51
+ self.messages.append(message)
52
+ return self.messages
53
+ ```
54
+
55
+ Public `def` methods become synchronous RPCs. Annotated fields persist by default. `emitted()` persists a field and broadcasts its saved changes; `ephemeral()` keeps a field temporary. Mutable defaults are copied for each actor, and both helpers accept `default_factory` for values constructed on activation. Use `ephemeral(default_factory=...)` for locks, caches, and service clients, and `ClassVar` for class constants. Classes extend `Actor` directly and use field defaults instead of constructors. Prefix helper methods with `_`. Synchronous handlers run on a worker thread, with calls serialized per actor. Socket hooks can also use ordinary `def`; `self.get_connections()` returns typed sockets.
56
+
57
+ Add `@reentrant` (imported from `durable_actors`) to a `def` method to let other invocations enter before it finishes. Synchronous reentrant handlers overlap on worker threads; coordinate shared mutations and keep blocking I/O outside shared locks. Ordinary calls still serialize with each other. As in TypeScript, enabling reentrancy disables error rollback for the entire actor class. See the [execution semantics](../docs/reference/python.md#execution-and-failures) for details.
58
+
59
+ ## Generate and use a client
60
+
61
+ With the actor server running, generate clients in your application:
62
+
63
+ ```sh
64
+ pnpm add -D durable-actors
65
+ uv add 'durable-actors[codegen]'
66
+ pnpm exec durable-actors generate --out-dir generated
67
+ ```
68
+
69
+ You can also generate directly from a trusted source entrypoint:
70
+
71
+ ```sh
72
+ pnpm exec durable-actors generate src/actors.py --out-dir generated
73
+ ```
74
+
75
+ ```python
76
+ from generated import actors
77
+
78
+ chat = actors.Chat.get("lobby")
79
+ messages: actors.Chat.Methods.append.Result = chat.append(actors.Chat.Message(text="hello"))
80
+ print(messages[0].text)
81
+ ```
82
+
83
+ The CLI runs strict mypy on local actor definitions before generation and on the generated package afterward. `dev` checks definitions before startup and every reload; an invalid edit leaves the previous code running.
84
+
85
+ Generated RPC methods return typed values directly. The SDK creates a shared HTTP client when needed, reads configuration from the environment, and closes its connection pool at process exit. You only need the actor ID.
86
+
87
+ The generated package exposes `actors`, matching the TypeScript client namespace. Use `actors.Chat.get(id)` for a handle, `actors.Chat.Stub` for its type, and `actors.Chat.Methods.append.Args` / `.Result` for method types. Socket types live at `actors.Chat.Metadata`, `.Incoming`, `.Outgoing`, and `.State`; concrete models such as `actors.Chat.Message` are also available there. The package includes docstrings, independent Pydantic models, and `py.typed`. Consumers need only `durable-actors`, not the actor project or the code generator. Regenerate after changing the actor contract, and include the generated package in your application's type checks.
88
+
89
+ Typing uses inline annotations and the [PEP 561](https://peps.python.org/pep-0561/) package marker. Both mypy and Pyright check the SDK and generated clients. Pydantic validates inputs, outputs, and persisted state at runtime. Python annotations remain ordinary annotations: `chat.append(42)` is rejected by a type checker and by runtime validation.
90
+
91
+ ## Subscribe to state
92
+
93
+ Actors with emitted fields have a typed `subscribe` method:
94
+
95
+ ```python
96
+ chat = actors.Chat.get("lobby")
97
+ subscription = chat.subscribe(lambda state: print(state.messages))
98
+ chat.append(actors.Chat.Message(text="hello"))
99
+ ```
100
+
101
+ The SDK receives the initial state and applies later patches in the background. Each callback gets a complete typed snapshot of the emitted fields, and RPC calls continue normally. Call `subscription.close()` when finished. Callbacks run serially on a background thread; the subscription does not keep an otherwise finished process alive.
102
+
103
+ Pass `on_error=handler` to handle connection, validation, or callback failures. A failure stops the subscription; its exception is available as `subscription.error` and is logged if no handler is supplied. Actors with required connection metadata also require `metadata=...` when subscribing.
104
+
105
+ See the [Python reference](https://github.com/TerseAI/durable-actors/blob/main/docs/reference/python.md) for supported types, sockets, reentrancy, deployment, and CLI options.
106
+
107
+ ## Resource settings and backend helpers
108
+
109
+ Use `@sandbox(cpu=2, memory_mib=2048, idle_timeout_ms=60_000, regions=["canada"])` above an actor class to override deployment defaults. Import `sandbox` from `durable_actors`.
110
+
111
+ Source classes also support `Chat.get("lobby")` with typed synchronous methods, including calls from other actors. Generated handles expose `broadcast(message)`; `actors.Chat.Authorization`, `actors.Chat.prepare_websocket(...)`, and `ActorProxy.handle(...)` issue typed browser access grants. `ActorSessionTransport` renews short-lived application credentials. The [Python reference](../docs/reference/python.md) covers these APIs and their docstrings.
@@ -0,0 +1,90 @@
1
+ # Little Actors for Python
2
+
3
+ Define durable actors in Python and generate typed Python clients. Python 3.11+ is supported. Actor state, placement, routing, and WebSocket delivery use the same Rust runtime as the TypeScript SDK.
4
+
5
+ ```sh
6
+ pnpm dlx durable-actors init my-actors --template python
7
+ cd my-actors
8
+ pnpm install
9
+ uv sync
10
+ pnpm exec durable-actors dev
11
+ ```
12
+
13
+ The shared TypeScript CLI manages development, generation, and Python type checking. It selects the project's `.venv` (or the active environment); `DURABLE_ACTORS_PYTHON` overrides the interpreter. Install Node.js 22+, Python 3.11+, and uv. The CLI downloads the matching native runtime on macOS and Linux. When developing this repository, set `DURABLE_ACTORS_BINARY` to an absolute path to your `cargo build --locked` executable.
14
+
15
+ ## Define actors
16
+
17
+ ```python
18
+ from pydantic import BaseModel
19
+ from durable_actors import Actor, emitted, ephemeral
20
+
21
+ class Message(BaseModel):
22
+ text: str
23
+
24
+ class Chat(Actor):
25
+ count: int = 0
26
+ messages: list[Message] = emitted(default_factory=list)
27
+ busy: bool = ephemeral(False)
28
+
29
+ def append(self, message: Message) -> list[Message]:
30
+ self.messages.append(message)
31
+ return self.messages
32
+ ```
33
+
34
+ Public `def` methods become synchronous RPCs. Annotated fields persist by default. `emitted()` persists a field and broadcasts its saved changes; `ephemeral()` keeps a field temporary. Mutable defaults are copied for each actor, and both helpers accept `default_factory` for values constructed on activation. Use `ephemeral(default_factory=...)` for locks, caches, and service clients, and `ClassVar` for class constants. Classes extend `Actor` directly and use field defaults instead of constructors. Prefix helper methods with `_`. Synchronous handlers run on a worker thread, with calls serialized per actor. Socket hooks can also use ordinary `def`; `self.get_connections()` returns typed sockets.
35
+
36
+ Add `@reentrant` (imported from `durable_actors`) to a `def` method to let other invocations enter before it finishes. Synchronous reentrant handlers overlap on worker threads; coordinate shared mutations and keep blocking I/O outside shared locks. Ordinary calls still serialize with each other. As in TypeScript, enabling reentrancy disables error rollback for the entire actor class. See the [execution semantics](../docs/reference/python.md#execution-and-failures) for details.
37
+
38
+ ## Generate and use a client
39
+
40
+ With the actor server running, generate clients in your application:
41
+
42
+ ```sh
43
+ pnpm add -D durable-actors
44
+ uv add 'durable-actors[codegen]'
45
+ pnpm exec durable-actors generate --out-dir generated
46
+ ```
47
+
48
+ You can also generate directly from a trusted source entrypoint:
49
+
50
+ ```sh
51
+ pnpm exec durable-actors generate src/actors.py --out-dir generated
52
+ ```
53
+
54
+ ```python
55
+ from generated import actors
56
+
57
+ chat = actors.Chat.get("lobby")
58
+ messages: actors.Chat.Methods.append.Result = chat.append(actors.Chat.Message(text="hello"))
59
+ print(messages[0].text)
60
+ ```
61
+
62
+ The CLI runs strict mypy on local actor definitions before generation and on the generated package afterward. `dev` checks definitions before startup and every reload; an invalid edit leaves the previous code running.
63
+
64
+ Generated RPC methods return typed values directly. The SDK creates a shared HTTP client when needed, reads configuration from the environment, and closes its connection pool at process exit. You only need the actor ID.
65
+
66
+ The generated package exposes `actors`, matching the TypeScript client namespace. Use `actors.Chat.get(id)` for a handle, `actors.Chat.Stub` for its type, and `actors.Chat.Methods.append.Args` / `.Result` for method types. Socket types live at `actors.Chat.Metadata`, `.Incoming`, `.Outgoing`, and `.State`; concrete models such as `actors.Chat.Message` are also available there. The package includes docstrings, independent Pydantic models, and `py.typed`. Consumers need only `durable-actors`, not the actor project or the code generator. Regenerate after changing the actor contract, and include the generated package in your application's type checks.
67
+
68
+ Typing uses inline annotations and the [PEP 561](https://peps.python.org/pep-0561/) package marker. Both mypy and Pyright check the SDK and generated clients. Pydantic validates inputs, outputs, and persisted state at runtime. Python annotations remain ordinary annotations: `chat.append(42)` is rejected by a type checker and by runtime validation.
69
+
70
+ ## Subscribe to state
71
+
72
+ Actors with emitted fields have a typed `subscribe` method:
73
+
74
+ ```python
75
+ chat = actors.Chat.get("lobby")
76
+ subscription = chat.subscribe(lambda state: print(state.messages))
77
+ chat.append(actors.Chat.Message(text="hello"))
78
+ ```
79
+
80
+ The SDK receives the initial state and applies later patches in the background. Each callback gets a complete typed snapshot of the emitted fields, and RPC calls continue normally. Call `subscription.close()` when finished. Callbacks run serially on a background thread; the subscription does not keep an otherwise finished process alive.
81
+
82
+ Pass `on_error=handler` to handle connection, validation, or callback failures. A failure stops the subscription; its exception is available as `subscription.error` and is logged if no handler is supplied. Actors with required connection metadata also require `metadata=...` when subscribing.
83
+
84
+ See the [Python reference](https://github.com/TerseAI/durable-actors/blob/main/docs/reference/python.md) for supported types, sockets, reentrancy, deployment, and CLI options.
85
+
86
+ ## Resource settings and backend helpers
87
+
88
+ Use `@sandbox(cpu=2, memory_mib=2048, idle_timeout_ms=60_000, regions=["canada"])` above an actor class to override deployment defaults. Import `sandbox` from `durable_actors`.
89
+
90
+ Source classes also support `Chat.get("lobby")` with typed synchronous methods, including calls from other actors. Generated handles expose `broadcast(message)`; `actors.Chat.Authorization`, `actors.Chat.prepare_websocket(...)`, and `ActorProxy.handle(...)` issue typed browser access grants. `ActorSessionTransport` renews short-lived application credentials. The [Python reference](../docs/reference/python.md) covers these APIs and their docstrings.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "durable-actors"
7
+ version = "0.5.5"
8
+ description = "Typed Python durable actors and generated clients"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ dependencies = ["pydantic>=2.12,<3", "typing-extensions>=4.12,<5", "httpx>=0.28,<1", "websockets>=15,<17", "jsonschema>=4.23,<5", "packaging>=24,<27"]
13
+
14
+ [project.urls]
15
+ Repository = "https://github.com/TerseAI/durable-actors"
16
+ Documentation = "https://github.com/TerseAI/durable-actors/blob/main/docs/reference/python.md"
17
+
18
+ [project.optional-dependencies]
19
+ codegen = ["datamodel-code-generator>=0.83,<0.84", "mypy>=1.15,<2", "pip>=25,<27"]
20
+
21
+ [dependency-groups]
22
+ dev = ["pytest>=8,<10", "pytest-asyncio>=1,<2", "mypy>=1.15,<2", "pyright>=1.1.400,<2", "ruff>=0.11,<1", "types-jsonschema", "datamodel-code-generator>=0.83,<0.84", "build>=1,<2", "pip>=25,<27"]
23
+
24
+ [tool.pytest.ini_options]
25
+ asyncio_mode = "auto"
26
+ testpaths = ["tests"]
27
+
28
+ [tool.mypy]
29
+ strict = true
30
+ files = ["src/durable_actors"]
31
+
32
+ [tool.pyright]
33
+ include = ["src/durable_actors"]
34
+ typeCheckingMode = "strict"
35
+
36
+ [tool.ruff]
37
+ line-length = 100
38
+ target-version = "py311"
39
+
40
+ [tool.ruff.lint]
41
+ select = ["E4", "E7", "E9", "F", "I"]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/durable_actors"]
@@ -0,0 +1,25 @@
1
+ """Define durable Python actors and use synchronous typed clients and subscriptions."""
2
+
3
+ from .actor import Actor as Actor
4
+ from .actor import reentrant as reentrant
5
+ from .client import ActorInvocationError as ActorInvocationError
6
+ from .client import ActorProtocolError as ActorProtocolError
7
+ from .client import ActorTransport as ActorTransport
8
+ from .client import Client as Client
9
+ from .client import SocketGrant as SocketGrant
10
+ from .connection import StateSnapshot as StateSnapshot
11
+ from .connection import StateUpdate as StateUpdate
12
+ from .fields import emitted as emitted
13
+ from .fields import ephemeral as ephemeral
14
+ from .generated import UNSET as UNSET
15
+ from .generated import Unset as Unset
16
+ from .json import JsonValue as JsonValue
17
+ from .proxy import SocketAuthorization as SocketAuthorization
18
+ from .sandbox import SandboxOptions as SandboxOptions
19
+ from .sandbox import SandboxRegion as SandboxRegion
20
+ from .sandbox import sandbox as sandbox
21
+ from .session import ActorSession as ActorSession
22
+ from .session import ActorSessionRejectedError as ActorSessionRejectedError
23
+ from .session import ActorSessionTransport as ActorSessionTransport
24
+ from .socket import ActorSocket as ActorSocket
25
+ from .subscription import Subscription as Subscription
@@ -0,0 +1,145 @@
1
+ """Define durable actors and control invocation concurrency."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from copy import deepcopy
7
+ from typing import TYPE_CHECKING, Any, Generic, Literal, Self, TypeVar
8
+
9
+ from typing_extensions import TypeVar as DefaultTypeVar
10
+
11
+ from .json import JsonValue
12
+
13
+ if TYPE_CHECKING:
14
+ from .client import ActorTransport
15
+ from .connection import Connection
16
+ from .socket import ActorSocket
17
+
18
+ Metadata = DefaultTypeVar("Metadata", default=JsonValue)
19
+ Incoming = DefaultTypeVar("Incoming", default=JsonValue)
20
+ Outgoing = DefaultTypeVar("Outgoing", default=Incoming)
21
+ Tag = DefaultTypeVar("Tag", bound=str, default=str)
22
+ F = TypeVar("F", bound=Callable[..., Any])
23
+
24
+
25
+ class Actor(Generic[Metadata, Incoming, Outgoing, Tag]):
26
+ """Base class for durable actors with typed RPCs and WebSocket hooks.
27
+
28
+ Public def methods become synchronous RPCs. Annotated fields persist by
29
+ default; emitted() also broadcasts their saved changes, and ephemeral() excludes
30
+ temporary values. Use field defaults or factories instead of a constructor.
31
+
32
+ Generic parameters describe connection metadata, incoming application
33
+ messages, outgoing application messages, and allowed connection tags. Outgoing
34
+ defaults to Incoming, tags to str, and other parameters to JsonValue.
35
+ """
36
+
37
+ def __init__(self) -> None:
38
+ """Initialize independent field defaults; the runtime restores persisted values afterward."""
39
+ from .contract import describe_actor
40
+
41
+ for field in describe_actor(type(self)).fields.values():
42
+ value = (
43
+ field.default_factory()
44
+ if field.default_factory is not None
45
+ else deepcopy(field.default)
46
+ )
47
+ setattr(self, field.name, value)
48
+
49
+ @classmethod
50
+ def get(cls, actor_id: str, transport: ActorTransport | None = None) -> Self:
51
+ """Return a typed synchronous reference without activating the actor locally.
52
+
53
+ RPC signatures match the source class. Fields and lifecycle hooks belong
54
+ to the running actor; read state through RPCs or generated subscriptions.
55
+ """
56
+ from .reference import actor_reference
57
+
58
+ return actor_reference(cls, actor_id, transport)
59
+
60
+ def connect(self, metadata: Metadata) -> Connection[Incoming, Outgoing, JsonValue, JsonValue]:
61
+ """Open a typed WebSocket through a source-class reference from get()."""
62
+ raise RuntimeError("connect() requires an actor reference from get()")
63
+
64
+ @property
65
+ def id(self) -> str:
66
+ """Identity of this actor, available during an active invocation or socket hook."""
67
+ from .socket import current_scope
68
+
69
+ return current_scope(self).actor_id
70
+
71
+ def on_connect(self, socket: ActorSocket[Metadata, Outgoing, Tag]) -> None:
72
+ """Handle a new WebSocket connection before it is accepted.
73
+
74
+ Override with def to inspect metadata, set tags, send a welcome
75
+ message, or reject with socket.reject(). The default accepts the connection.
76
+ """
77
+ pass
78
+
79
+ def on_message(self, socket: ActorSocket[Metadata, Outgoing, Tag], message: Incoming) -> None:
80
+ """Handle a validated incoming application message from a connected client.
81
+
82
+ Override with def to update actor state or send typed replies
83
+ through socket. The default ignores application messages.
84
+ """
85
+ pass
86
+
87
+ def on_disconnect(
88
+ self, socket: ActorSocket[Metadata, Outgoing, Tag], code: int, reason: str, was_clean: bool
89
+ ) -> None:
90
+ """Handle a closed connection; override with def for cleanup.
91
+
92
+ Args:
93
+ socket: Connection with its last known metadata and tags.
94
+ code: WebSocket close status code.
95
+ reason: WebSocket close reason.
96
+ was_clean: Whether the connection completed a clean closing handshake.
97
+ """
98
+ pass
99
+
100
+ def get_connections(self) -> list[ActorSocket[Metadata, Outgoing, Tag]]:
101
+ """Return connected sockets from a synchronous actor method or hook.
102
+
103
+ Use the returned handles only during the current invocation.
104
+ """
105
+ from .socket import current_scope
106
+
107
+ scope = current_scope(self)
108
+ return scope.blocking(scope.get_connections)
109
+
110
+ def broadcast(
111
+ self,
112
+ message: Outgoing,
113
+ *,
114
+ except_ids: tuple[str, ...] = (),
115
+ tags: tuple[Tag, ...] = (),
116
+ tag_match: Literal["all", "any"] = "all",
117
+ ) -> None:
118
+ """Queue a typed application message for selected connections.
119
+
120
+ Args:
121
+ message: Value matching the actor's outgoing message type.
122
+ except_ids: Connection IDs to exclude.
123
+ tags: Restrict delivery to matching tags; empty selects all connections.
124
+ tag_match: Use "all" to require every tag or "any" to require one.
125
+
126
+ Must be called during an active actor invocation or socket hook.
127
+ """
128
+ from .socket import current_scope
129
+
130
+ current_scope(self).broadcast(message, except_ids, tags, tag_match)
131
+
132
+
133
+ def reentrant(method: F) -> F:
134
+ """Allow other invocations to enter before this method finishes.
135
+
136
+ Works with def RPCs and socket hooks. Handlers overlap on worker threads.
137
+ Ordinary invocations still serialize with each other and block new entries.
138
+ Nested method calls inherit the outer invocation's admission policy.
139
+
140
+ Reentrant actors share a live instance. Coordinate shared mutable state
141
+ across overlapping handlers. Failed invocations do not roll back state
142
+ anywhere in the actor class, because that could erase another call's work.
143
+ """
144
+ setattr(method, "__actor_reentrant__", True)
145
+ return method