horsies 0.1.0a5__tar.gz → 0.1.0a6__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.
- {horsies-0.1.0a5 → horsies-0.1.0a6}/PKG-INFO +1 -1
- horsies-0.1.0a6/horsies/core/banner.py +258 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/PKG-INFO +1 -1
- {horsies-0.1.0a5 → horsies-0.1.0a6}/pyproject.toml +1 -1
- horsies-0.1.0a5/horsies/core/banner.py +0 -144
- {horsies-0.1.0a5 → horsies-0.1.0a6}/README.md +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/app.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/brokers/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/brokers/listener.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/brokers/postgres.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/cli.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/codec/serde.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/errors.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/logging.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/app.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/broker.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/queues.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/recovery.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/schedule.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/task_pg.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/tasks.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/workflow.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/models/workflow_pg.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/registry/tasks.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/scheduler/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/scheduler/calculator.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/scheduler/service.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/scheduler/state.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/task_decorator.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/types/status.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/utils/imports.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/utils/loop_runner.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/worker/current.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/worker/worker.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/workflows/__init__.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/workflows/engine.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/workflows/recovery.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/core/workflows/registry.py +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies/py.typed +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/SOURCES.txt +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/dependency_links.txt +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/entry_points.txt +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/requires.txt +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/horsies.egg-info/top_level.txt +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/setup.cfg +0 -0
- {horsies-0.1.0a5 → horsies-0.1.0a6}/tests/test_issue_fixes.py +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Startup banner for horsies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import TYPE_CHECKING, TextIO
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from horsies.core.app import Horsies
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# ANSI color codes (matching Rust owo-colors output)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
class Colors:
|
|
17
|
+
"""ANSI escape codes for terminal colors."""
|
|
18
|
+
|
|
19
|
+
RESET = '\033[0m'
|
|
20
|
+
BOLD = '\033[1m'
|
|
21
|
+
DIMMED = '\033[2m'
|
|
22
|
+
|
|
23
|
+
# Standard colors
|
|
24
|
+
WHITE = '\033[37m'
|
|
25
|
+
BRIGHT_WHITE = '\033[97m'
|
|
26
|
+
CYAN = '\033[36m'
|
|
27
|
+
BRIGHT_CYAN = '\033[96m'
|
|
28
|
+
YELLOW = '\033[33m'
|
|
29
|
+
BRIGHT_YELLOW = '\033[93m'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _color(text: str, *codes: str) -> str:
|
|
33
|
+
"""Apply ANSI color codes to text."""
|
|
34
|
+
if not codes:
|
|
35
|
+
return text
|
|
36
|
+
return ''.join(codes) + text + Colors.RESET
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Braille art of galloping horse - converted from the golden horse image
|
|
40
|
+
# Each braille character represents a 2x4 pixel block for higher resolution
|
|
41
|
+
HORSE_BRAILLE = """
|
|
42
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣾⣿⣿⣿⣿⣷⣯⡀⠀⠀⠀⠀⠀⠀
|
|
43
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣿⣆⠀⠀⠀⠀
|
|
44
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⡟⠹⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀
|
|
45
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣠⣴⣾⣿⣿⣿⣶⣶⣶⣤⣤⣤⣤⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠈⠉⠛⠻⡿⣿⣿⠂⠀
|
|
46
|
+
⠀⠀⢀⣀⠀⢀⣀⣠⣶⣿⣿⠟⢛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⡐⠀⠀⠀⠀⠀⠀⠈⠋⡿⠁⠀⠀
|
|
47
|
+
⠀⠀⠀⢹⣿⣿⣿⣿⣿⡿⠁⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
48
|
+
⠀⠀⠀⠀⠛⠻⠿⠛⠉⠀⠀⠀⠈⢯⡻⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
49
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣷⣿⣿⣿⡟⠀⠙⠻⠿⠿⣿⣿⠃⣿⣿⣿⣿⣿⣿⣿⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
50
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡿⠁⢀⠀⠀⠀⠀⠀⠂⠀⢿⣿⣿⣿⡍⠈⢁⣙⣿⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
51
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⠏⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣷⠀⠀⠀⠀⠁⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
52
|
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣧⣀⢄⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣧⠀⠀⢀⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
53
|
+
⠀⠀⠀⣀⠀⠀⠀⢀⡀⠀⠀⠀⡀⠀⠀⠀⣀⠛⣿⣷⣍⠛⣦⡀⠀⠂⢠⣷⣶⣾⡿⠟⠛⢃⠀⢠⣾⡟⠀⠀⠀⠀⡀⠀⠀⠀⡀⠀⠀⠀
|
|
54
|
+
⠄⡤⠦⠤⠤⢤⡤⠡⠤⠤⢤⠬⠤⠤⠤⢤⠅⠀⠤⣿⣷⠄⠎⢻⣤⡦⠄⠀⠤⢵⠄⠠⠤⠬⣾⠏⠁⠀⠥⡤⠄⠠⠬⢦⡤⠀⠠⠵⢤⠠
|
|
55
|
+
⠚⠒⠒⠒⡶⠚⠒⠒⠒⡰⠓⠒⠒⠒⢲⠓⠒⠒⠒⢻⠿⠀⠀⠚⢿⡷⠐⠒⠒⠚⡆⠒⠒⠒⠚⣖⠒⠒⠒⠚⢶⠒⠒⠒⠚⢶⠂⠐⠒⠛
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Figlet-style "horsies" text with version and tagline
|
|
59
|
+
LOGO_TEXT = r"""
|
|
60
|
+
__ _
|
|
61
|
+
/ /_ ____ __________(_)__ _____
|
|
62
|
+
/ __ \/ __ \/ ___/ ___/ / _ \/ ___/ {version}
|
|
63
|
+
/ / / / /_/ / / (__ ) / __(__ ) distributed task queue
|
|
64
|
+
/_/ /_/\____/_/ /____/_/\___/____/ and workflow engine
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Full banner combining horse and logo
|
|
68
|
+
BANNER = (
|
|
69
|
+
HORSE_BRAILLE
|
|
70
|
+
+ r"""
|
|
71
|
+
__ _
|
|
72
|
+
/ /_ ____ __________(_)__ _____
|
|
73
|
+
/ __ \/ __ \/ ___/ ___/ / _ \/ ___/ {version}
|
|
74
|
+
/ / / / /_/ / / (__ ) / __(__ ) distributed task queue
|
|
75
|
+
/_/ /_/\____/_/ /____/_/\___/____/ and workflow engine
|
|
76
|
+
"""
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_version() -> str:
|
|
81
|
+
"""Get horsies version from package metadata."""
|
|
82
|
+
try:
|
|
83
|
+
from importlib.metadata import version
|
|
84
|
+
return version('horsies')
|
|
85
|
+
except Exception:
|
|
86
|
+
return 'dev'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def format_banner(version: str | None = None) -> str:
|
|
90
|
+
"""Format the banner string with version."""
|
|
91
|
+
if version is None:
|
|
92
|
+
version = get_version()
|
|
93
|
+
return BANNER.format(version=f'v{version}')
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Formatting helpers (matching Rust banner.rs)
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def _format_ms(ms: int) -> str:
|
|
101
|
+
"""Format milliseconds into a human-readable string."""
|
|
102
|
+
if ms >= 60_000:
|
|
103
|
+
mins = ms // 60_000
|
|
104
|
+
remainder_s = (ms % 60_000) // 1_000
|
|
105
|
+
if remainder_s > 0:
|
|
106
|
+
return f'{mins}m{remainder_s}s'
|
|
107
|
+
return f'{mins}m'
|
|
108
|
+
elif ms >= 1_000:
|
|
109
|
+
secs = ms // 1_000
|
|
110
|
+
remainder_ms = ms % 1_000
|
|
111
|
+
if remainder_ms > 0:
|
|
112
|
+
return f'{secs}.{remainder_ms // 100}s'
|
|
113
|
+
return f'{secs}s'
|
|
114
|
+
return f'{ms}ms'
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _format_bool(val: bool) -> str:
|
|
118
|
+
"""Format a boolean for display."""
|
|
119
|
+
return 'yes' if val else 'no'
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _write_section_header(lines: list[str], name: str) -> None:
|
|
123
|
+
"""Write a colored [section] header line."""
|
|
124
|
+
header = _color(name, Colors.BRIGHT_CYAN, Colors.BOLD)
|
|
125
|
+
lines.append(f'[{header}]')
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _write_kv(lines: list[str], key: str, value: str) -> None:
|
|
129
|
+
"""Write a colored '.> key: value' line with consistent alignment."""
|
|
130
|
+
prefix = _color('.>', Colors.DIMMED)
|
|
131
|
+
# Pad key before coloring to maintain alignment
|
|
132
|
+
padded_key = f'{key}:'.ljust(22)
|
|
133
|
+
colored_key = _color(padded_key, Colors.WHITE, Colors.BOLD)
|
|
134
|
+
colored_value = _color(value, Colors.BRIGHT_WHITE)
|
|
135
|
+
lines.append(f' {prefix} {colored_key} {colored_value}')
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def print_banner(
|
|
139
|
+
app: 'Horsies',
|
|
140
|
+
role: str = 'worker',
|
|
141
|
+
show_tasks: bool = True,
|
|
142
|
+
file: TextIO | None = None,
|
|
143
|
+
) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Print startup banner with configuration and task list.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
app: The Horsies app instance
|
|
149
|
+
role: The role (worker, scheduler, producer)
|
|
150
|
+
show_tasks: Whether to list discovered tasks
|
|
151
|
+
file: Output file (default: sys.stdout)
|
|
152
|
+
"""
|
|
153
|
+
if file is None:
|
|
154
|
+
file = sys.stdout
|
|
155
|
+
|
|
156
|
+
version = get_version()
|
|
157
|
+
|
|
158
|
+
# Build the banner
|
|
159
|
+
lines: list[str] = []
|
|
160
|
+
|
|
161
|
+
# ASCII art header - cyan colored
|
|
162
|
+
colored_horse = _color(HORSE_BRAILLE, Colors.CYAN)
|
|
163
|
+
lines.append(colored_horse)
|
|
164
|
+
|
|
165
|
+
# Logo with version - bright yellow bold
|
|
166
|
+
logo = LOGO_TEXT.replace('{version}', f'v{version}')
|
|
167
|
+
colored_logo = _color(logo, Colors.BRIGHT_YELLOW, Colors.BOLD)
|
|
168
|
+
lines.append(colored_logo)
|
|
169
|
+
|
|
170
|
+
# [config] section
|
|
171
|
+
_write_section_header(lines, 'config')
|
|
172
|
+
_write_kv(lines, 'app', app.__class__.__name__)
|
|
173
|
+
_write_kv(lines, 'role', role)
|
|
174
|
+
_write_kv(lines, 'queue_mode', app.config.queue_mode.name.lower())
|
|
175
|
+
|
|
176
|
+
# Queue info
|
|
177
|
+
if app.config.queue_mode.name == 'CUSTOM' and app.config.custom_queues:
|
|
178
|
+
queues_str = ', '.join(q.name for q in app.config.custom_queues)
|
|
179
|
+
else:
|
|
180
|
+
queues_str = 'default'
|
|
181
|
+
_write_kv(lines, 'queues', queues_str)
|
|
182
|
+
|
|
183
|
+
# Broker info (mask password)
|
|
184
|
+
broker_url = app.config.broker.database_url
|
|
185
|
+
if '@' in broker_url:
|
|
186
|
+
pre, post = broker_url.split('@', 1)
|
|
187
|
+
if ':' in pre:
|
|
188
|
+
scheme_user = pre.rsplit(':', 1)[0]
|
|
189
|
+
broker_url = f'{scheme_user}:****@{post}'
|
|
190
|
+
_write_kv(lines, 'broker', broker_url)
|
|
191
|
+
|
|
192
|
+
# Cluster-wide cap
|
|
193
|
+
if hasattr(app.config, 'cluster_wide_cap') and app.config.cluster_wide_cap:
|
|
194
|
+
_write_kv(lines, 'cluster_cap', f'{app.config.cluster_wide_cap} (cluster-wide)')
|
|
195
|
+
|
|
196
|
+
# Prefetch buffer
|
|
197
|
+
if hasattr(app.config, 'prefetch_buffer') and app.config.prefetch_buffer > 0:
|
|
198
|
+
_write_kv(lines, 'prefetch', str(app.config.prefetch_buffer))
|
|
199
|
+
|
|
200
|
+
lines.append('')
|
|
201
|
+
|
|
202
|
+
# [recovery] section
|
|
203
|
+
if hasattr(app.config, 'recovery') and app.config.recovery:
|
|
204
|
+
recovery = app.config.recovery
|
|
205
|
+
_write_section_header(lines, 'recovery')
|
|
206
|
+
_write_kv(
|
|
207
|
+
lines,
|
|
208
|
+
'requeue_stale_claimed',
|
|
209
|
+
_format_bool(recovery.auto_requeue_stale_claimed),
|
|
210
|
+
)
|
|
211
|
+
_write_kv(
|
|
212
|
+
lines,
|
|
213
|
+
'fail_stale_running',
|
|
214
|
+
_format_bool(recovery.auto_fail_stale_running),
|
|
215
|
+
)
|
|
216
|
+
_write_kv(
|
|
217
|
+
lines,
|
|
218
|
+
'check_interval',
|
|
219
|
+
_format_ms(recovery.check_interval_ms),
|
|
220
|
+
)
|
|
221
|
+
_write_kv(
|
|
222
|
+
lines,
|
|
223
|
+
'heartbeat_interval',
|
|
224
|
+
_format_ms(recovery.runner_heartbeat_interval_ms),
|
|
225
|
+
)
|
|
226
|
+
lines.append('')
|
|
227
|
+
|
|
228
|
+
# [tasks] section
|
|
229
|
+
if show_tasks:
|
|
230
|
+
task_names = app.list_tasks()
|
|
231
|
+
tasks_header = _color('tasks', Colors.BRIGHT_CYAN, Colors.BOLD)
|
|
232
|
+
lines.append(f'[{tasks_header}] ({len(task_names)} registered)')
|
|
233
|
+
for task_name in sorted(task_names):
|
|
234
|
+
bullet = _color('.', Colors.DIMMED)
|
|
235
|
+
name = _color(task_name, Colors.WHITE)
|
|
236
|
+
lines.append(f' {bullet} {name}')
|
|
237
|
+
lines.append('')
|
|
238
|
+
|
|
239
|
+
# Print everything
|
|
240
|
+
output = '\n'.join(lines)
|
|
241
|
+
print(output, file=file)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def print_simple_banner(file: TextIO | None = None) -> None:
|
|
245
|
+
"""Print just the ASCII art banner without config."""
|
|
246
|
+
if file is None:
|
|
247
|
+
file = sys.stdout
|
|
248
|
+
|
|
249
|
+
version = get_version()
|
|
250
|
+
|
|
251
|
+
# Horse art - cyan
|
|
252
|
+
colored_horse = _color(HORSE_BRAILLE, Colors.CYAN)
|
|
253
|
+
print(colored_horse, file=file)
|
|
254
|
+
|
|
255
|
+
# Logo with version - bright yellow bold
|
|
256
|
+
logo = LOGO_TEXT.replace('{version}', f'v{version}')
|
|
257
|
+
colored_logo = _color(logo, Colors.BRIGHT_YELLOW, Colors.BOLD)
|
|
258
|
+
print(colored_logo, file=file)
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
"""Startup banner for horsies."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
from typing import TYPE_CHECKING, TextIO
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from horsies.core.app import Horsies
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Braille art of galloping horse - converted from the golden horse image
|
|
13
|
-
# Each braille character represents a 2x4 pixel block for higher resolution
|
|
14
|
-
HORSE_BRAILLE = """
|
|
15
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣾⣿⣿⣿⣿⣷⣯⡀⠀⠀⠀⠀⠀⠀
|
|
16
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣿⣆⠀⠀⠀⠀
|
|
17
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⡟⠹⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀
|
|
18
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣠⣴⣾⣿⣿⣿⣶⣶⣶⣤⣤⣤⣤⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠈⠉⠛⠻⡿⣿⣿⠂⠀
|
|
19
|
-
⠀⠀⢀⣀⠀⢀⣀⣠⣶⣿⣿⠟⢛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⡐⠀⠀⠀⠀⠀⠀⠈⠋⡿⠁⠀⠀
|
|
20
|
-
⠀⠀⠀⢹⣿⣿⣿⣿⣿⡿⠁⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
21
|
-
⠀⠀⠀⠀⠛⠻⠿⠛⠉⠀⠀⠀⠈⢯⡻⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
22
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣷⣿⣿⣿⡟⠀⠙⠻⠿⠿⣿⣿⠃⣿⣿⣿⣿⣿⣿⣿⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
23
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡿⠁⢀⠀⠀⠀⠀⠀⠂⠀⢿⣿⣿⣿⡍⠈⢁⣙⣿⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
24
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⠏⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣷⠀⠀⠀⠀⠁⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
25
|
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣧⣀⢄⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣧⠀⠀⢀⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
26
|
-
⠀⠀⠀⣀⠀⠀⠀⢀⡀⠀⠀⠀⡀⠀⠀⠀⣀⠛⣿⣷⣍⠛⣦⡀⠀⠂⢠⣷⣶⣾⡿⠟⠛⢃⠀⢠⣾⡟⠀⠀⠀⠀⡀⠀⠀⠀⡀⠀⠀⠀
|
|
27
|
-
⠄⡤⠦⠤⠤⢤⡤⠡⠤⠤⢤⠬⠤⠤⠤⢤⠅⠀⠤⣿⣷⠄⠎⢻⣤⡦⠄⠀⠤⢵⠄⠠⠤⠬⣾⠏⠁⠀⠥⡤⠄⠠⠬⢦⡤⠀⠠⠵⢤⠠
|
|
28
|
-
⠚⠒⠒⠒⡶⠚⠒⠒⠒⡰⠓⠒⠒⠒⢲⠓⠒⠒⠒⢻⠿⠀⠀⠚⢿⡷⠐⠒⠒⠚⡆⠒⠒⠒⠚⣖⠒⠒⠒⠚⢶⠒⠒⠒⠚⢶⠂⠐⠒⠛
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
# Figlet-style "horsies" text
|
|
32
|
-
LOGO_TEXT = r"""
|
|
33
|
-
__ _
|
|
34
|
-
/ /_ ____ __________(_)__ _____
|
|
35
|
-
/ __ \/ __ \/ ___/ ___/ / _ \/ ___/
|
|
36
|
-
/ / / / /_/ / / (__ ) / __(__ )
|
|
37
|
-
/_/ /_/\____/_/ /____/_/\___/____/
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
# Full banner combining horse and logo
|
|
41
|
-
BANNER = (
|
|
42
|
-
HORSE_BRAILLE
|
|
43
|
-
+ r"""
|
|
44
|
-
__ _
|
|
45
|
-
/ /_ ____ __________(_)__ _____
|
|
46
|
-
/ __ \/ __ \/ ___/ ___/ / _ \/ ___/ {version}
|
|
47
|
-
/ / / / /_/ / / (__ ) / __(__ ) distributed task queue
|
|
48
|
-
/_/ /_/\____/_/ /____/_/\___/____/ and workflow engine
|
|
49
|
-
"""
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def get_version() -> str:
|
|
54
|
-
"""Get horsies version."""
|
|
55
|
-
try:
|
|
56
|
-
import horsies
|
|
57
|
-
|
|
58
|
-
version = getattr(horsies, '__version__', None)
|
|
59
|
-
return str(version) if version else '0.1.0'
|
|
60
|
-
except ImportError:
|
|
61
|
-
return '0.1.0'
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def format_banner(version: str | None = None) -> str:
|
|
65
|
-
"""Format the banner string with version."""
|
|
66
|
-
if version is None:
|
|
67
|
-
version = get_version()
|
|
68
|
-
return BANNER.format(version=f'v{version}')
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def print_banner(
|
|
72
|
-
app: 'Horsies',
|
|
73
|
-
role: str = 'worker',
|
|
74
|
-
show_tasks: bool = True,
|
|
75
|
-
file: TextIO | None = None,
|
|
76
|
-
) -> None:
|
|
77
|
-
"""
|
|
78
|
-
Print startup banner with configuration and task list.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
app: The Horsies app instance
|
|
82
|
-
role: The role (worker, scheduler, producer)
|
|
83
|
-
show_tasks: Whether to list discovered tasks
|
|
84
|
-
file: Output file (default: sys.stdout)
|
|
85
|
-
"""
|
|
86
|
-
if file is None:
|
|
87
|
-
file = sys.stdout
|
|
88
|
-
|
|
89
|
-
version = get_version()
|
|
90
|
-
|
|
91
|
-
# Build the banner
|
|
92
|
-
lines: list[str] = []
|
|
93
|
-
|
|
94
|
-
# ASCII art header
|
|
95
|
-
banner = format_banner(version)
|
|
96
|
-
lines.append(banner)
|
|
97
|
-
|
|
98
|
-
# Configuration section
|
|
99
|
-
lines.append('[config]')
|
|
100
|
-
lines.append(f' .> app: {app.__class__.__name__}')
|
|
101
|
-
lines.append(f' .> role: {role}')
|
|
102
|
-
lines.append(f' .> queue_mode: {app.config.queue_mode.name}')
|
|
103
|
-
|
|
104
|
-
# Queue info
|
|
105
|
-
if app.config.queue_mode.name == 'CUSTOM' and app.config.custom_queues:
|
|
106
|
-
queues_str = ', '.join(q.name for q in app.config.custom_queues)
|
|
107
|
-
lines.append(f' .> queues: {queues_str}')
|
|
108
|
-
else:
|
|
109
|
-
lines.append(f' .> queues: default')
|
|
110
|
-
|
|
111
|
-
# Broker info
|
|
112
|
-
broker_url = app.config.broker.database_url
|
|
113
|
-
# Mask password in URL
|
|
114
|
-
if '@' in broker_url:
|
|
115
|
-
pre, post = broker_url.split('@', 1)
|
|
116
|
-
if ':' in pre:
|
|
117
|
-
scheme_user = pre.rsplit(':', 1)[0]
|
|
118
|
-
broker_url = f'{scheme_user}:****@{post}'
|
|
119
|
-
lines.append(f' .> broker: {broker_url}')
|
|
120
|
-
|
|
121
|
-
# Concurrency info (if available)
|
|
122
|
-
if hasattr(app.config, 'cluster_wide_cap') and app.config.cluster_wide_cap:
|
|
123
|
-
lines.append(f' .> cap: {app.config.cluster_wide_cap} (cluster-wide)')
|
|
124
|
-
|
|
125
|
-
lines.append('')
|
|
126
|
-
|
|
127
|
-
# Tasks section
|
|
128
|
-
if show_tasks:
|
|
129
|
-
task_names = app.list_tasks()
|
|
130
|
-
lines.append(f'[tasks] ({len(task_names)} registered)')
|
|
131
|
-
for task_name in sorted(task_names):
|
|
132
|
-
lines.append(f' . {task_name}')
|
|
133
|
-
lines.append('')
|
|
134
|
-
|
|
135
|
-
# Print everything
|
|
136
|
-
output = '\n'.join(lines)
|
|
137
|
-
print(output, file=file)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def print_simple_banner(file: TextIO | None = None) -> None:
|
|
141
|
-
"""Print just the ASCII art banner without config."""
|
|
142
|
-
if file is None:
|
|
143
|
-
file = sys.stdout
|
|
144
|
-
print(format_banner(), file=file)
|
|
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
|