unrealon 0.1.17__tar.gz → 0.1.19__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 (108) hide show
  1. {unrealon-0.1.17 → unrealon-0.1.19}/PKG-INFO +1 -1
  2. unrealon-0.1.19/github/README.md +230 -0
  3. {unrealon-0.1.17 → unrealon-0.1.19}/github/pyproject.toml +1 -1
  4. {unrealon-0.1.17 → unrealon-0.1.19}/pyproject.toml +1 -1
  5. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/__init__.py +3 -0
  6. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/__init__.py +2 -0
  7. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/enums.py +16 -0
  8. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/models.py +11 -18
  9. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_version.py +1 -1
  10. unrealon-0.1.19/src/unrealon/runner.py +190 -0
  11. unrealon-0.1.17/github/README.md +0 -209
  12. {unrealon-0.1.17 → unrealon-0.1.19}/.gitignore +0 -0
  13. {unrealon-0.1.17 → unrealon-0.1.19}/README.md +0 -0
  14. {unrealon-0.1.17 → unrealon-0.1.19}/examples/README.md +0 -0
  15. {unrealon-0.1.17 → unrealon-0.1.19}/github/unrealon/_api/generated/services/pyproject.toml +0 -0
  16. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/__init__.py +0 -0
  17. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/client.py +0 -0
  18. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/__init__.py +0 -0
  19. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/client.py +0 -0
  20. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/__init__.py +0 -0
  21. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/logger.py +0 -0
  22. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/retry.py +0 -0
  23. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/pyproject.toml +0 -0
  24. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/__init__.py +0 -0
  25. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/client.py +0 -0
  26. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/models.py +0 -0
  27. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/sync_client.py +0 -0
  28. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/__init__.py +0 -0
  29. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/client.py +0 -0
  30. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/models.py +0 -0
  31. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/sync_client.py +0 -0
  32. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/__init__.py +0 -0
  33. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/client.py +0 -0
  34. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/models.py +0 -0
  35. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/sync_client.py +0 -0
  36. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/__init__.py +0 -0
  37. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/client.py +0 -0
  38. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/models.py +0 -0
  39. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/sync_client.py +0 -0
  40. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/__init__.py +0 -0
  41. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/client.py +0 -0
  42. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/models.py +0 -0
  43. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/sync_client.py +0 -0
  44. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/__init__.py +0 -0
  45. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/client.py +0 -0
  46. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/sync_client.py +0 -0
  47. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/__init__.py +0 -0
  48. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/client.py +0 -0
  49. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/models.py +0 -0
  50. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/sync_client.py +0 -0
  51. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/__init__.py +0 -0
  52. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/client.py +0 -0
  53. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/models.py +0 -0
  54. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/sync_client.py +0 -0
  55. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/__init__.py +0 -0
  56. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/client.py +0 -0
  57. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/models.py +0 -0
  58. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/sync_client.py +0 -0
  59. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/__init__.py +0 -0
  60. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/client.py +0 -0
  61. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/models.py +0 -0
  62. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/sync_client.py +0 -0
  63. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/__init__.py +0 -0
  64. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/client.py +0 -0
  65. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/models.py +0 -0
  66. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/sync_client.py +0 -0
  67. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_api/generated/services/sync_client.py +0 -0
  68. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_client.py +0 -0
  69. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_config.py +0 -0
  70. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/_constants.py +0 -0
  71. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/core/__init__.py +0 -0
  72. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/core/lifecycle.py +0 -0
  73. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/core/signals.py +0 -0
  74. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/core/state.py +0 -0
  75. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/exceptions/__init__.py +0 -0
  76. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/exceptions/handlers.py +0 -0
  77. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/exceptions/types.py +0 -0
  78. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/__init__.py +0 -0
  79. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_config.py +0 -0
  80. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_connection.py +0 -0
  81. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_constants.py +0 -0
  82. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_handlers.py +0 -0
  83. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_logging.py +0 -0
  84. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_messaging.py +0 -0
  85. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_metrics.py +0 -0
  86. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_reconnect.py +0 -0
  87. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_registration.py +0 -0
  88. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/_types.py +0 -0
  89. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/circuit_breaker.py +0 -0
  90. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/generated/__init__.py +0 -0
  91. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2.py +0 -0
  92. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2.pyi +0 -0
  93. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2_grpc.py +0 -0
  94. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/grpc/stream_service.py +0 -0
  95. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/__init__.py +0 -0
  96. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/_config.py +0 -0
  97. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/_formatters.py +0 -0
  98. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/_handlers.py +0 -0
  99. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/_logger.py +0 -0
  100. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/logging/_project.py +0 -0
  101. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/models/__init__.py +0 -0
  102. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/scheduling/__init__.py +0 -0
  103. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/scheduling/_manager.py +0 -0
  104. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/scheduling/_models.py +0 -0
  105. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/services/__init__.py +0 -0
  106. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/utils/__init__.py +0 -0
  107. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/utils/metrics.py +0 -0
  108. {unrealon-0.1.17 → unrealon-0.1.19}/src/unrealon/utils/system.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unrealon
3
- Version: 0.1.17
3
+ Version: 0.1.19
4
4
  Summary: Unrealon SDK - Service management for Django backend (registration, heartbeat, logging, commands)
5
5
  Project-URL: Homepage, https://github.com/markolofsen/unrealon-sdk
6
6
  Project-URL: Documentation, https://unrealon.com
@@ -0,0 +1,230 @@
1
+ # Unrealon SDK
2
+
3
+ Python SDK for monitoring and managing services via the Unrealon platform.
4
+
5
+ ## Features
6
+
7
+ - **Monitoring** — real-time service status visibility
8
+ - **Cloud Logs** — all logs accessible in the web interface
9
+ - **Control** — pause/resume/stop directly from the dashboard
10
+ - **Metrics** — counters for processed items and errors
11
+ - **Scheduling** — automatic cron-based task execution
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install unrealon
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### Minimal Example
22
+
23
+ ```python
24
+ from unrealon import ServiceClient
25
+
26
+ with ServiceClient(api_key="pk_xxx", service_name="my-service") as client:
27
+ client.info("Started")
28
+
29
+ for item in items:
30
+ process(item)
31
+ client.increment_processed()
32
+
33
+ client.info("Done")
34
+ ```
35
+
36
+ The service will register, logs will stream to cloud, and metrics will be displayed.
37
+
38
+ ### With Pause/Resume Support
39
+
40
+ ```python
41
+ from unrealon import ServiceClient
42
+
43
+ with ServiceClient(api_key="pk_xxx", service_name="my-parser") as client:
44
+ client.info("Started")
45
+
46
+ for item in items:
47
+ client.check_interrupt() # Parser pauses here if Pause is clicked
48
+
49
+ process(item)
50
+ client.increment_processed()
51
+
52
+ client.info("Done")
53
+ ```
54
+
55
+ `check_interrupt()` does two things:
56
+ - If **Pause** was clicked — waits until Resume
57
+ - If **Stop** was clicked — raises `StopInterrupt`
58
+
59
+ ## Continuous Mode
60
+
61
+ A service that waits for commands from the dashboard:
62
+
63
+ ```python
64
+ import time
65
+ from unrealon import ServiceClient
66
+ from unrealon.exceptions import StopInterrupt
67
+
68
+ with ServiceClient(api_key="pk_xxx", service_name="my-parser") as client:
69
+
70
+ def handle_run(params: dict) -> dict:
71
+ limit = params.get("limit", 100)
72
+
73
+ client.set_busy()
74
+ try:
75
+ for i in range(limit):
76
+ client.check_interrupt()
77
+ do_work()
78
+ client.increment_processed()
79
+ return {"status": "ok"}
80
+ except StopInterrupt:
81
+ return {"status": "stopped"}
82
+ finally:
83
+ client.set_idle()
84
+
85
+ client.on_command("run", handle_run)
86
+
87
+ # Wait for commands
88
+ client.set_idle()
89
+ while not client.should_stop:
90
+ time.sleep(1)
91
+ ```
92
+
93
+ Now from the dashboard you can:
94
+ - Click **Run** — executes `handle_run`
95
+ - Click **Pause** — parser stops at `check_interrupt()`
96
+ - Click **Resume** — continues from where it left off
97
+ - Click **Stop** — graceful shutdown
98
+
99
+ ## API
100
+
101
+ ### Logging
102
+
103
+ ```python
104
+ client.debug("Debug message")
105
+ client.info("Info message", key="value")
106
+ client.warning("Warning")
107
+ client.error("Error", code=500)
108
+ ```
109
+
110
+ Logs go to three places: console (Rich), file, and cloud.
111
+
112
+ ### Metrics
113
+
114
+ ```python
115
+ client.increment_processed() # +1 processed
116
+ client.increment_processed(10) # +10 processed
117
+ client.increment_errors() # +1 error
118
+ ```
119
+
120
+ ### Status
121
+
122
+ ```python
123
+ client.set_busy() # Shows "Busy" in dashboard
124
+ client.set_idle() # Shows "Idle"
125
+ ```
126
+
127
+ ### State
128
+
129
+ ```python
130
+ client.is_paused # True if paused
131
+ client.should_stop # True if stop requested
132
+ client.is_connected # True if connected to server
133
+ ```
134
+
135
+ ### Commands
136
+
137
+ ```python
138
+ # Register handler
139
+ client.on_command("run", handle_run)
140
+ client.on_command("custom", handle_custom)
141
+
142
+ # Handler receives params and returns result
143
+ def handle_run(params: dict) -> dict:
144
+ limit = params.get("limit", 10)
145
+ # ... do work ...
146
+ return {"status": "ok", "processed": 100}
147
+ ```
148
+
149
+ ### Schedules
150
+
151
+ Schedules run automatically based on cron expressions.
152
+ If the schedule's `action_type` matches a registered command,
153
+ the same handler is used:
154
+
155
+ ```python
156
+ # This handler works for both manual Run and scheduled runs
157
+ client.on_command("run", handle_run)
158
+ ```
159
+
160
+ For different behavior, register a schedule-specific handler:
161
+
162
+ ```python
163
+ @client.on_schedule("process")
164
+ def handle_scheduled_process(schedule, params):
165
+ # schedule.name, schedule.id are available
166
+ return {"items_processed": 100}
167
+ ```
168
+
169
+ ## Configuration
170
+
171
+ ### Via Environment Variables
172
+
173
+ ```bash
174
+ export UNREALON_API_KEY=pk_xxx
175
+ export UNREALON_SERVICE_NAME=my-service
176
+ ```
177
+
178
+ ```python
179
+ # Picks up from env
180
+ with ServiceClient() as client:
181
+ ...
182
+ ```
183
+
184
+ ### Dev Mode (Local Server)
185
+
186
+ ```python
187
+ with ServiceClient(
188
+ api_key="dk_xxx",
189
+ service_name="my-service",
190
+ dev_mode=True, # Connects to localhost:50051
191
+ ) as client:
192
+ ...
193
+ ```
194
+
195
+ ## Exceptions
196
+
197
+ ```python
198
+ from unrealon.exceptions import (
199
+ StopInterrupt, # Stop requested (inherits BaseException!)
200
+ UnrealonError, # Base SDK error
201
+ AuthenticationError, # Bad API key
202
+ RegistrationError, # Can't register
203
+ )
204
+
205
+ try:
206
+ with ServiceClient(...) as client:
207
+ for item in items:
208
+ client.check_interrupt()
209
+ process(item)
210
+ except StopInterrupt:
211
+ print("Stopped by command")
212
+ ```
213
+
214
+ **Important**: `StopInterrupt` inherits from `BaseException`, not `Exception`.
215
+ This means `except Exception` won't catch it — by design, so generic
216
+ error handlers don't swallow the stop command.
217
+
218
+ ## Standalone Logger
219
+
220
+ You can use the logger separately from the SDK:
221
+
222
+ ```python
223
+ from unrealon.logging import get_logger
224
+
225
+ log = get_logger("myapp")
226
+ log.info("Starting", version="1.0")
227
+ log.error("Failed", error="connection timeout")
228
+ ```
229
+
230
+ Logs go to console and file (without cloud).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unrealon"
3
- version = "0.1.14"
3
+ version = "0.1.18"
4
4
  description = "Lightweight Python SDK for managing distributed workers"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "unrealon"
7
- version = "0.1.17"
7
+ version = "0.1.19"
8
8
  description = "Unrealon SDK - Service management for Django backend (registration, heartbeat, logging, commands)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -27,6 +27,7 @@ Example:
27
27
 
28
28
  from ._client import AsyncServiceClient, ServiceClient
29
29
  from ._config import UnrealonConfig, configure, get_config, reset_config
30
+ from .runner import TaskRunner
30
31
  from ._version import __version__
31
32
  from .core import (
32
33
  LifecycleConfig,
@@ -96,4 +97,6 @@ __all__ = [
96
97
  "Schedule",
97
98
  "ScheduleResult",
98
99
  "ScheduleRunStatus",
100
+ # Runner
101
+ "TaskRunner",
99
102
  ]
@@ -36,6 +36,7 @@ from .enums import (
36
36
  CommandCommandType,
37
37
  CommandStatus,
38
38
  LogEntryRequestLevel,
39
+ PatchedScheduleCreateRequestActionType,
39
40
  PatchedServiceDetailRequestConnectionType,
40
41
  ProcessJobJobType,
41
42
  ProcessJobStatus,
@@ -285,6 +286,7 @@ __all__ = [
285
286
  "CommandCommandType",
286
287
  "CommandStatus",
287
288
  "LogEntryRequestLevel",
289
+ "PatchedScheduleCreateRequestActionType",
288
290
  "PatchedServiceDetailRequestConnectionType",
289
291
  "ProcessJobJobType",
290
292
  "ProcessJobStatus",
@@ -99,6 +99,22 @@ class LogEntryRequestLevel(StrEnum):
99
99
 
100
100
 
101
101
 
102
+ class PatchedScheduleCreateRequestActionType(StrEnum):
103
+ """
104
+ Action type to execute
105
+ * `run` - Run (execute parser)
106
+ * `pause` - Pause service
107
+ * `resume` - Resume service
108
+ * `custom` - Custom action
109
+ """
110
+
111
+ RUN = "run"
112
+ PAUSE = "pause"
113
+ RESUME = "resume"
114
+ CUSTOM = "custom"
115
+
116
+
117
+
102
118
  class PatchedServiceDetailRequestConnectionType(StrEnum):
103
119
  """
104
120
  How to control this service (local, cloud, ssh, or none)
@@ -6,7 +6,7 @@ from typing import Any
6
6
 
7
7
  from pydantic import BaseModel, ConfigDict, Field
8
8
 
9
- from ..enums import ScheduleEventEventType, ScheduleRunStatus
9
+ from ..enums import PatchedScheduleCreateRequestActionType, ScheduleEventEventType, ScheduleRunStatus
10
10
 
11
11
 
12
12
  class Schedule(BaseModel):
@@ -34,10 +34,9 @@ class Schedule(BaseModel):
34
34
  max_length=100,
35
35
  )
36
36
  frequency_display: Any = ...
37
- action_type: str | None = Field(
37
+ action_type: PatchedScheduleCreateRequestActionType | None = Field(
38
38
  None,
39
- description='Action type to execute (process,...',
40
- max_length=50,
39
+ description='Action type to execute * `run` ...',
41
40
  )
42
41
  next_run_at: str | None = Field(None, description='Next scheduled execution time')
43
42
  last_run_at: Any | None = Field(None, description='Last execution time')
@@ -105,11 +104,9 @@ class ScheduleCreateRequest(BaseModel):
105
104
  min_length=1,
106
105
  max_length=50,
107
106
  )
108
- action_type: str | None = Field(
107
+ action_type: PatchedScheduleCreateRequestActionType | None = Field(
109
108
  None,
110
- description='Action type to execute (process,...',
111
- min_length=1,
112
- max_length=50,
109
+ description='Action type to execute * `run` ...',
113
110
  )
114
111
  action_params: dict[str, Any] | None = Field(
115
112
  None,
@@ -163,10 +160,9 @@ class ScheduleCreate(BaseModel):
163
160
  description="Timezone for schedule (e.g., 'Eu...",
164
161
  max_length=50,
165
162
  )
166
- action_type: str | None = Field(
163
+ action_type: PatchedScheduleCreateRequestActionType | None = Field(
167
164
  None,
168
- description='Action type to execute (process,...',
169
- max_length=50,
165
+ description='Action type to execute * `run` ...',
170
166
  )
171
167
  action_params: dict[str, Any] | None = Field(
172
168
  None,
@@ -223,10 +219,9 @@ class ScheduleDetail(BaseModel):
223
219
  description="Timezone for schedule (e.g., 'Eu...",
224
220
  max_length=50,
225
221
  )
226
- action_type: str | None = Field(
222
+ action_type: PatchedScheduleCreateRequestActionType | None = Field(
227
223
  None,
228
- description='Action type to execute (process,...',
229
- max_length=50,
224
+ description='Action type to execute * `run` ...',
230
225
  )
231
226
  action_params: dict[str, Any] | None = Field(
232
227
  None,
@@ -294,11 +289,9 @@ class PatchedScheduleCreateRequest(BaseModel):
294
289
  min_length=1,
295
290
  max_length=50,
296
291
  )
297
- action_type: str | None = Field(
292
+ action_type: PatchedScheduleCreateRequestActionType | None = Field(
298
293
  None,
299
- description='Action type to execute (process,...',
300
- min_length=1,
301
- max_length=50,
294
+ description='Action type to execute * `run` ...',
302
295
  )
303
296
  action_params: dict[str, Any] | None = Field(
304
297
  None,
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "0.1.14"
3
+ __version__ = "0.1.19"
@@ -0,0 +1,190 @@
1
+ """
2
+ Task runner with automatic interrupt handling.
3
+
4
+ Provides simple primitives for running interruptible tasks.
5
+ The runner automatically checks for pause/stop commands between iterations.
6
+
7
+ Example:
8
+ ```python
9
+ from unrealon import ServiceClient, TaskRunner
10
+
11
+ with ServiceClient(...) as client:
12
+ runner = TaskRunner(client)
13
+
14
+ # Simple iteration - auto-checks interrupt between items
15
+ for car in runner.iterate(cars):
16
+ process_car(car)
17
+
18
+ # With checkpoint for long operations
19
+ for page in runner.iterate(range(1, 10)):
20
+ data = fetch_page(page) # Long operation
21
+ runner.checkpoint() # Check after fetch
22
+ parse_data(data) # Another operation
23
+ runner.checkpoint() # Check after parse
24
+ ```
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ from collections.abc import Iterable, Iterator
31
+ from typing import TYPE_CHECKING, Any, TypeVar
32
+
33
+ if TYPE_CHECKING:
34
+ from ._client import ServiceClient
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ T = TypeVar("T")
39
+
40
+
41
+ class TaskRunner:
42
+ """
43
+ Runner for interruptible tasks.
44
+
45
+ Wraps iteration and long operations with automatic pause/stop handling.
46
+ No need to manually call check_interrupt() - the runner does it for you.
47
+
48
+ Features:
49
+ - `iterate(items)` - Yields items, checking interrupt between each
50
+ - `checkpoint()` - Explicit check point for long operations
51
+ - `run(func, *args)` - Run function with interrupt check before/after
52
+
53
+ Example:
54
+ ```python
55
+ runner = TaskRunner(client)
56
+
57
+ # Automatically stops/pauses between items
58
+ for item in runner.iterate(items):
59
+ process(item)
60
+
61
+ # Manual checkpoints for fine-grained control
62
+ for batch in runner.iterate(batches):
63
+ result = slow_operation(batch)
64
+ runner.checkpoint() # Check after slow op
65
+ save_result(result)
66
+ ```
67
+ """
68
+
69
+ __slots__ = ("_client", "_current_item", "_items_processed")
70
+
71
+ def __init__(self, client: ServiceClient) -> None:
72
+ """
73
+ Initialize task runner.
74
+
75
+ Args:
76
+ client: ServiceClient instance (must be started)
77
+ """
78
+ self._client = client
79
+ self._current_item: Any = None
80
+ self._items_processed: int = 0
81
+
82
+ @property
83
+ def items_processed(self) -> int:
84
+ """Number of items processed in current run."""
85
+ return self._items_processed
86
+
87
+ @property
88
+ def is_paused(self) -> bool:
89
+ """Check if currently paused."""
90
+ return self._client.is_paused
91
+
92
+ @property
93
+ def is_stopping(self) -> bool:
94
+ """Check if stop was requested."""
95
+ return self._client.shutdown_requested
96
+
97
+ def iterate(self, items: Iterable[T]) -> Iterator[T]:
98
+ """
99
+ Iterate over items with automatic interrupt handling.
100
+
101
+ Checks for pause/stop before yielding each item.
102
+ If paused, waits until resumed or stopped.
103
+ If stopped, raises StopInterrupt.
104
+
105
+ Args:
106
+ items: Iterable to iterate over (list, range, generator, etc.)
107
+
108
+ Yields:
109
+ Items from the iterable
110
+
111
+ Raises:
112
+ StopInterrupt: If stop was requested
113
+
114
+ Example:
115
+ ```python
116
+ # Simple - just iterate
117
+ for car in runner.iterate(cars):
118
+ process_car(car)
119
+
120
+ # With progress tracking
121
+ for i, page in enumerate(runner.iterate(range(1, 100))):
122
+ client.info(f"Processing page {i+1}")
123
+ fetch_page(page)
124
+ ```
125
+ """
126
+ for item in items:
127
+ # Check before processing each item
128
+ self._client.check_interrupt()
129
+ self._current_item = item
130
+ yield item
131
+ self._items_processed += 1
132
+
133
+ def checkpoint(self) -> None:
134
+ """
135
+ Explicit interrupt check point.
136
+
137
+ Call this during long operations to allow interruption.
138
+ If paused, blocks until resumed.
139
+ If stopped, raises StopInterrupt.
140
+
141
+ Example:
142
+ ```python
143
+ for url in runner.iterate(urls):
144
+ data = fetch(url) # Might take 30s
145
+ runner.checkpoint() # Allow interrupt here
146
+ parsed = parse(data) # Might take 10s
147
+ runner.checkpoint() # And here
148
+ save(parsed)
149
+ ```
150
+ """
151
+ self._client.check_interrupt()
152
+
153
+ def run(self, func: Any, *args: Any, **kwargs: Any) -> Any:
154
+ """
155
+ Run function with interrupt checks before and after.
156
+
157
+ Args:
158
+ func: Function to run
159
+ *args: Positional arguments
160
+ **kwargs: Keyword arguments
161
+
162
+ Returns:
163
+ Function result
164
+
165
+ Raises:
166
+ StopInterrupt: If stop was requested
167
+
168
+ Example:
169
+ ```python
170
+ # Instead of:
171
+ # check_interrupt()
172
+ # result = slow_function(arg)
173
+ # check_interrupt()
174
+
175
+ # Just:
176
+ result = runner.run(slow_function, arg)
177
+ ```
178
+ """
179
+ self._client.check_interrupt()
180
+ result = func(*args, **kwargs)
181
+ self._client.check_interrupt()
182
+ return result
183
+
184
+ def reset(self) -> None:
185
+ """Reset counters for new run."""
186
+ self._items_processed = 0
187
+ self._current_item = None
188
+
189
+
190
+ __all__ = ["TaskRunner"]
@@ -1,209 +0,0 @@
1
- # Unrealon SDK
2
-
3
- Python SDK для мониторинга и управления сервисами через Unrealon платформу.
4
-
5
- ## Что даёт SDK
6
-
7
- - **Мониторинг** — видишь статус сервиса в реальном времени
8
- - **Логи в облако** — все логи доступны в веб-интерфейсе
9
- - **Управление** — pause/resume/stop прямо из дашборда
10
- - **Метрики** — счётчики обработанных элементов и ошибок
11
-
12
- ## Установка
13
-
14
- ```bash
15
- pip install unrealon
16
- ```
17
-
18
- ## Быстрый старт
19
-
20
- ### Минимальный пример
21
-
22
- ```python
23
- from unrealon import ServiceClient
24
-
25
- with ServiceClient(api_key="pk_xxx", service_name="my-service") as client:
26
- client.info("Started")
27
-
28
- for item in items:
29
- process(item)
30
- client.increment_processed()
31
-
32
- client.info("Done")
33
- ```
34
-
35
- Всё. Сервис зарегистрируется, логи пойдут в облако, метрики будут отображаться.
36
-
37
- ### С поддержкой pause/resume
38
-
39
- ```python
40
- from unrealon import ServiceClient
41
-
42
- with ServiceClient(api_key="pk_xxx", service_name="my-parser") as client:
43
- client.info("Started")
44
-
45
- for item in items:
46
- client.check_interrupt() # Тут парсер встанет на паузу если нажать Pause
47
-
48
- process(item)
49
- client.increment_processed()
50
-
51
- client.info("Done")
52
- ```
53
-
54
- `check_interrupt()` делает две вещи:
55
- - Если нажали **Pause** — ждёт пока нажмут Resume
56
- - Если нажали **Stop** — выбрасывает `StopInterrupt`
57
-
58
- ## Continuous Mode
59
-
60
- Сервис который ждёт команд из дашборда:
61
-
62
- ```python
63
- import time
64
- from unrealon import ServiceClient
65
- from unrealon.exceptions import StopInterrupt
66
-
67
- with ServiceClient(api_key="pk_xxx", service_name="my-parser") as client:
68
-
69
- def handle_run(params: dict) -> dict:
70
- limit = params.get("limit", 100)
71
-
72
- client.set_busy()
73
- try:
74
- for i in range(limit):
75
- client.check_interrupt()
76
- do_work()
77
- client.increment_processed()
78
- return {"status": "ok"}
79
- except StopInterrupt:
80
- return {"status": "stopped"}
81
- finally:
82
- client.set_idle()
83
-
84
- client.on_command("run", handle_run)
85
-
86
- # Ждём команд
87
- client.set_idle()
88
- while not client.should_stop:
89
- time.sleep(1)
90
- ```
91
-
92
- Теперь можно из дашборда:
93
- - Нажать **Run** — запустится `handle_run`
94
- - Нажать **Pause** — парсер встанет на `check_interrupt()`
95
- - Нажать **Resume** — продолжит с того же места
96
- - Нажать **Stop** — завершится gracefully
97
-
98
- ## API
99
-
100
- ### Логирование
101
-
102
- ```python
103
- client.debug("Debug message")
104
- client.info("Info message", key="value")
105
- client.warning("Warning")
106
- client.error("Error", code=500)
107
- ```
108
-
109
- Логи идут в три места: консоль (Rich), файл, облако.
110
-
111
- ### Метрики
112
-
113
- ```python
114
- client.increment_processed() # +1 обработано
115
- client.increment_processed(10) # +10 обработано
116
- client.increment_errors() # +1 ошибка
117
- ```
118
-
119
- ### Статусы
120
-
121
- ```python
122
- client.set_busy() # Показывает "Busy" в дашборде
123
- client.set_idle() # Показывает "Idle"
124
- ```
125
-
126
- ### Состояние
127
-
128
- ```python
129
- client.is_paused # True если на паузе
130
- client.should_stop # True если запрошена остановка
131
- client.is_connected # True если подключен к серверу
132
- ```
133
-
134
- ### Команды
135
-
136
- ```python
137
- # Регистрация обработчика
138
- client.on_command("run", handle_run)
139
- client.on_command("custom", handle_custom)
140
-
141
- # Обработчик получает params и возвращает результат
142
- def handle_run(params: dict) -> dict:
143
- limit = params.get("limit", 10)
144
- # ... do work ...
145
- return {"status": "ok", "processed": 100}
146
- ```
147
-
148
- ## Конфигурация
149
-
150
- ### Через переменные окружения
151
-
152
- ```bash
153
- export UNREALON_API_KEY=pk_xxx
154
- export UNREALON_SERVICE_NAME=my-service
155
- ```
156
-
157
- ```python
158
- # Подхватит из env
159
- with ServiceClient() as client:
160
- ...
161
- ```
162
-
163
- ### Dev mode (локальный сервер)
164
-
165
- ```python
166
- with ServiceClient(
167
- api_key="dk_xxx",
168
- service_name="my-service",
169
- dev_mode=True, # Подключится к localhost:50051
170
- ) as client:
171
- ...
172
- ```
173
-
174
- ## Exceptions
175
-
176
- ```python
177
- from unrealon.exceptions import (
178
- StopInterrupt, # Stop requested (наследует BaseException!)
179
- UnrealonError, # Base SDK error
180
- AuthenticationError, # Bad API key
181
- RegistrationError, # Can't register
182
- )
183
-
184
- try:
185
- with ServiceClient(...) as client:
186
- for item in items:
187
- client.check_interrupt()
188
- process(item)
189
- except StopInterrupt:
190
- print("Stopped by command")
191
- ```
192
-
193
- **Важно**: `StopInterrupt` наследует `BaseException`, не `Exception`.
194
- Это значит что `except Exception` его НЕ поймает — специально, чтобы
195
- generic error handlers не глотали команду stop.
196
-
197
- ## Standalone Logger
198
-
199
- Можно использовать логгер отдельно от SDK:
200
-
201
- ```python
202
- from unrealon.logging import get_logger
203
-
204
- log = get_logger("myapp")
205
- log.info("Starting", version="1.0")
206
- log.error("Failed", error="connection timeout")
207
- ```
208
-
209
- Логи пойдут в консоль и файл (без облака).
File without changes
File without changes
File without changes