unrealon 0.1.18__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.
- {unrealon-0.1.18 → unrealon-0.1.19}/PKG-INFO +1 -1
- unrealon-0.1.19/github/README.md +230 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/github/pyproject.toml +1 -1
- {unrealon-0.1.18 → unrealon-0.1.19}/pyproject.toml +1 -1
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/__init__.py +3 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/__init__.py +2 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/enums.py +16 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/models.py +11 -18
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_version.py +1 -1
- unrealon-0.1.19/src/unrealon/runner.py +190 -0
- unrealon-0.1.18/github/README.md +0 -209
- {unrealon-0.1.18 → unrealon-0.1.19}/.gitignore +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/README.md +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/examples/README.md +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/github/unrealon/_api/generated/services/pyproject.toml +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/logger.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/retry.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/pyproject.toml +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__api_keys/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_control/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__process_jobs/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_events/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedule_runs/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__schedules/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_commands/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_control/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_logs/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__service_sdk/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/services__api__services/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/sync_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_client.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_config.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_constants.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/core/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/core/lifecycle.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/core/signals.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/core/state.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/exceptions/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/exceptions/handlers.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/exceptions/types.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_config.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_connection.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_constants.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_handlers.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_logging.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_messaging.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_metrics.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_reconnect.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_registration.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/_types.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/circuit_breaker.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/generated/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2.pyi +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/generated/unrealon_pb2_grpc.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/grpc/stream_service.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/_config.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/_formatters.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/_handlers.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/_logger.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/logging/_project.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/models/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/scheduling/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/scheduling/_manager.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/scheduling/_models.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/services/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/utils/__init__.py +0 -0
- {unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/utils/metrics.py +0 -0
- {unrealon-0.1.18 → 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.
|
|
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).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unrealon"
|
|
7
|
-
version = "0.1.
|
|
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:
|
|
37
|
+
action_type: PatchedScheduleCreateRequestActionType | None = Field(
|
|
38
38
|
None,
|
|
39
|
-
description='Action type to execute
|
|
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:
|
|
107
|
+
action_type: PatchedScheduleCreateRequestActionType | None = Field(
|
|
109
108
|
None,
|
|
110
|
-
description='Action type to execute
|
|
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:
|
|
163
|
+
action_type: PatchedScheduleCreateRequestActionType | None = Field(
|
|
167
164
|
None,
|
|
168
|
-
description='Action type to execute
|
|
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:
|
|
222
|
+
action_type: PatchedScheduleCreateRequestActionType | None = Field(
|
|
227
223
|
None,
|
|
228
|
-
description='Action type to execute
|
|
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:
|
|
292
|
+
action_type: PatchedScheduleCreateRequestActionType | None = Field(
|
|
298
293
|
None,
|
|
299
|
-
description='Action type to execute
|
|
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,
|
|
@@ -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"]
|
unrealon-0.1.18/github/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unrealon-0.1.18 → unrealon-0.1.19}/src/unrealon/_api/generated/services/helpers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|