tay-client 0.7.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.
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrii Sydorenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: tay-client
3
+ Version: 0.7.0
4
+ Summary: Python SDK for producing and executing Tay jobs
5
+ Author: Andrii Sydorenko
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/AndriiSydorenko1904/tay/tree/v0.7.0/clients/python
8
+ Project-URL: Issues, https://github.com/AndriiSydorenko1904/tay/issues
9
+ Project-URL: Source, https://github.com/AndriiSydorenko1904/tay
10
+ Keywords: background-jobs,job-queue,scheduler,unix-socket
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # Tay Python SDK
29
+
30
+ `tay` is the stdlib-only Protocol v1 producer and worker client for a local Tay
31
+ Unix-domain socket. `Tay()` needs no socket setting: it uses the same discovery
32
+ contract as the Elixir Engine (explicit path, `TAY_SOCKET_PATH`, XDG runtime,
33
+ `TMPDIR`, then `/tmp/tay-<uid>`). It is intentionally a separately installable
34
+ package:
35
+
36
+ ```sh
37
+ python -m pip install tay-client
38
+ ```
39
+
40
+ From a Tay source checkout, use `python -m pip install ./clients/python`.
41
+
42
+ Create a `Tay(mode="client")` producer, or a `Tay(mode="worker")` process that
43
+ only executes registered tasks. The SDK sends JSON-object arguments and bounded
44
+ JSON results; it does not serialize arbitrary Python objects.
45
+
46
+ The producer surface is asynchronous: `enqueue`, `JobHandle.status()`,
47
+ `JobHandle.result()`, and `JobHandle.cancel()` map directly to the currently
48
+ supported Protocol v1 requests. Use a stable `submission_id` when retrying an
49
+ enqueue whose connection outcome is unknown.
50
+
51
+ Tasks declared before `await tay.start()` are advertised during the handshake.
52
+ For a long-running worker that adds or removes capabilities later, explicitly
53
+ sync the change:
54
+
55
+ ```python
56
+ @tay.task(name="reports.rebuild.v1")
57
+ def rebuild(report_id: str) -> dict:
58
+ return {"report_id": report_id}
59
+
60
+
61
+ await tay.register_tasks(rebuild)
62
+ # ... stop accepting new work for this task
63
+ await tay.unregister_tasks(rebuild)
64
+ ```
65
+
66
+ Both changes are retained as desired capability state and reconciled on the
67
+ next connection. `client` mode cannot register task capabilities.
68
+
69
+ ## Schedules
70
+
71
+ `schedule()` uses the five-field Unix cron form (minute, hour, day of month,
72
+ month, day of week). Its default timezone is `+00`; pass a fixed UTC offset
73
+ such as `+02`, `-05`, or `+05:30` when the schedule should be evaluated in a
74
+ different local clock. The API accepts `catch_up="latest"` (default) or
75
+ `catch_up="all"`, and defaults to `overlap="skip"`.
76
+
77
+ ```python
78
+ # Every weekday at 08:00 in UTC+02.
79
+ await tay.schedule(
80
+ "reports.rebuild.v1",
81
+ cron="0 8 * * 1-5",
82
+ timezone="+02",
83
+ catch_up="latest",
84
+ )
85
+
86
+ # First run in ten minutes, then every fifteen minutes.
87
+ await tay.every("reports.rebuild.v1", minutes=15, delay=600)
88
+ ```
89
+
90
+ Use `start_at=<UTC milliseconds>` instead of `delay` when the first occurrence
91
+ has an absolute timestamp. The two options are mutually exclusive. Schedule
92
+ creation, execution, and cancellation work against the current listener
93
+ generation. Durable recovery/catch-up and enforced overlap policies are still
94
+ pending in the Tay engine.
95
+
96
+ Run a dedicated worker module with:
97
+
98
+ ```sh
99
+ tay-worker myapp.jobs
100
+ ```
101
+
102
+ The imported module must create exactly one `Tay(mode="worker")` instance and
103
+ register its tasks. Pass `socket_path=` or set `TAY_SOCKET_PATH` only to override
104
+ automatic discovery. See the repository README for the current durable-result/
105
+ scheduling compatibility boundary.
106
+
107
+ ## License
108
+
109
+ The `tay-client` Python package is licensed under the MIT License. The Tay
110
+ engine is distributed separately under the Elastic License 2.0.
@@ -0,0 +1,83 @@
1
+ # Tay Python SDK
2
+
3
+ `tay` is the stdlib-only Protocol v1 producer and worker client for a local Tay
4
+ Unix-domain socket. `Tay()` needs no socket setting: it uses the same discovery
5
+ contract as the Elixir Engine (explicit path, `TAY_SOCKET_PATH`, XDG runtime,
6
+ `TMPDIR`, then `/tmp/tay-<uid>`). It is intentionally a separately installable
7
+ package:
8
+
9
+ ```sh
10
+ python -m pip install tay-client
11
+ ```
12
+
13
+ From a Tay source checkout, use `python -m pip install ./clients/python`.
14
+
15
+ Create a `Tay(mode="client")` producer, or a `Tay(mode="worker")` process that
16
+ only executes registered tasks. The SDK sends JSON-object arguments and bounded
17
+ JSON results; it does not serialize arbitrary Python objects.
18
+
19
+ The producer surface is asynchronous: `enqueue`, `JobHandle.status()`,
20
+ `JobHandle.result()`, and `JobHandle.cancel()` map directly to the currently
21
+ supported Protocol v1 requests. Use a stable `submission_id` when retrying an
22
+ enqueue whose connection outcome is unknown.
23
+
24
+ Tasks declared before `await tay.start()` are advertised during the handshake.
25
+ For a long-running worker that adds or removes capabilities later, explicitly
26
+ sync the change:
27
+
28
+ ```python
29
+ @tay.task(name="reports.rebuild.v1")
30
+ def rebuild(report_id: str) -> dict:
31
+ return {"report_id": report_id}
32
+
33
+
34
+ await tay.register_tasks(rebuild)
35
+ # ... stop accepting new work for this task
36
+ await tay.unregister_tasks(rebuild)
37
+ ```
38
+
39
+ Both changes are retained as desired capability state and reconciled on the
40
+ next connection. `client` mode cannot register task capabilities.
41
+
42
+ ## Schedules
43
+
44
+ `schedule()` uses the five-field Unix cron form (minute, hour, day of month,
45
+ month, day of week). Its default timezone is `+00`; pass a fixed UTC offset
46
+ such as `+02`, `-05`, or `+05:30` when the schedule should be evaluated in a
47
+ different local clock. The API accepts `catch_up="latest"` (default) or
48
+ `catch_up="all"`, and defaults to `overlap="skip"`.
49
+
50
+ ```python
51
+ # Every weekday at 08:00 in UTC+02.
52
+ await tay.schedule(
53
+ "reports.rebuild.v1",
54
+ cron="0 8 * * 1-5",
55
+ timezone="+02",
56
+ catch_up="latest",
57
+ )
58
+
59
+ # First run in ten minutes, then every fifteen minutes.
60
+ await tay.every("reports.rebuild.v1", minutes=15, delay=600)
61
+ ```
62
+
63
+ Use `start_at=<UTC milliseconds>` instead of `delay` when the first occurrence
64
+ has an absolute timestamp. The two options are mutually exclusive. Schedule
65
+ creation, execution, and cancellation work against the current listener
66
+ generation. Durable recovery/catch-up and enforced overlap policies are still
67
+ pending in the Tay engine.
68
+
69
+ Run a dedicated worker module with:
70
+
71
+ ```sh
72
+ tay-worker myapp.jobs
73
+ ```
74
+
75
+ The imported module must create exactly one `Tay(mode="worker")` instance and
76
+ register its tasks. Pass `socket_path=` or set `TAY_SOCKET_PATH` only to override
77
+ automatic discovery. See the repository README for the current durable-result/
78
+ scheduling compatibility boundary.
79
+
80
+ ## License
81
+
82
+ The `tay-client` Python package is licensed under the MIT License. The Tay
83
+ engine is distributed separately under the Elastic License 2.0.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tay-client"
7
+ version = "0.7.0"
8
+ description = "Python SDK for producing and executing Tay jobs"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "Andrii Sydorenko"}]
14
+ keywords = ["background-jobs", "job-queue", "scheduler", "unix-socket"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: MacOS",
19
+ "Operating System :: POSIX",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Typing :: Typed",
28
+ ]
29
+
30
+ [project.urls]
31
+ Documentation = "https://github.com/AndriiSydorenko1904/tay/tree/v0.7.0/clients/python"
32
+ Issues = "https://github.com/AndriiSydorenko1904/tay/issues"
33
+ Source = "https://github.com/AndriiSydorenko1904/tay"
34
+
35
+ [project.scripts]
36
+ tay-worker = "tay.__main__:main"
37
+
38
+ [tool.setuptools]
39
+ packages = ["tay"]
40
+
41
+ [tool.setuptools.package-data]
42
+ tay = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """Public Python SDK for the Tay Protocol v1 local executor endpoint."""
2
+
3
+ from .client import JobHandle, ScheduleHandle, Tay
4
+ from .errors import (
5
+ ConnectionLost,
6
+ ModeError,
7
+ ProtocolError,
8
+ RemoteTaskError,
9
+ ResultTooLarge,
10
+ ServerError,
11
+ TaskNotRegistered,
12
+ TaskRegistrationError,
13
+ TayError,
14
+ ValidationError,
15
+ )
16
+ from .socket_path import resolve_socket_path
17
+ from .task import Task
18
+
19
+ __all__ = [
20
+ "ConnectionLost",
21
+ "JobHandle",
22
+ "ModeError",
23
+ "ProtocolError",
24
+ "RemoteTaskError",
25
+ "ResultTooLarge",
26
+ "ScheduleHandle",
27
+ "ServerError",
28
+ "Task",
29
+ "TaskNotRegistered",
30
+ "TaskRegistrationError",
31
+ "Tay",
32
+ "TayError",
33
+ "ValidationError",
34
+ "resolve_socket_path",
35
+ ]
@@ -0,0 +1,67 @@
1
+ """The small, dependency-free dedicated-worker entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import importlib
8
+ import signal
9
+ import sys
10
+ from typing import Any
11
+
12
+ from .client import Tay
13
+
14
+
15
+ def _instance(specification: str) -> Tay:
16
+ module_name, separator, attribute = specification.partition(":")
17
+ module = importlib.import_module(module_name)
18
+ if separator:
19
+ candidate: Any = getattr(module, attribute)
20
+ if not isinstance(candidate, Tay):
21
+ raise TypeError(f"{specification!r} is not a Tay instance")
22
+ return candidate
23
+
24
+ candidates = tuple(
25
+ instance for instance in Tay.instances() if instance.__module__ == "tay.client"
26
+ )
27
+ if len(candidates) != 1:
28
+ raise RuntimeError(
29
+ f"{module_name!r} must create exactly one Tay instance, or use module:attribute"
30
+ )
31
+ return candidates[0]
32
+
33
+
34
+ async def _serve(instance: Tay) -> None:
35
+ if instance.mode != "worker":
36
+ raise RuntimeError("tay-worker requires Tay(mode='worker')")
37
+
38
+ stopped = asyncio.Event()
39
+ loop = asyncio.get_running_loop()
40
+ for current in (signal.SIGINT, signal.SIGTERM):
41
+ try:
42
+ loop.add_signal_handler(current, stopped.set)
43
+ except (
44
+ NotImplementedError
45
+ ): # pragma: no cover - Windows has no UDS support here
46
+ pass
47
+
48
+ await instance.start()
49
+ try:
50
+ await stopped.wait()
51
+ finally:
52
+ await instance.close()
53
+
54
+
55
+ def main(argv: list[str] | None = None) -> int:
56
+ parser = argparse.ArgumentParser(description="run a dedicated Tay Python worker")
57
+ parser.add_argument("application", help="module or module:Tay_instance to import")
58
+ arguments = parser.parse_args(argv)
59
+ try:
60
+ asyncio.run(_serve(_instance(arguments.application)))
61
+ except (ImportError, AttributeError, RuntimeError, TypeError) as exc:
62
+ parser.error(str(exc))
63
+ return 0
64
+
65
+
66
+ if __name__ == "__main__": # pragma: no cover
67
+ sys.exit(main())