brawny 0.1.13__py3-none-any.whl
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.
- brawny/__init__.py +106 -0
- brawny/_context.py +232 -0
- brawny/_rpc/__init__.py +38 -0
- brawny/_rpc/broadcast.py +172 -0
- brawny/_rpc/clients.py +98 -0
- brawny/_rpc/context.py +49 -0
- brawny/_rpc/errors.py +252 -0
- brawny/_rpc/gas.py +158 -0
- brawny/_rpc/manager.py +982 -0
- brawny/_rpc/selector.py +156 -0
- brawny/accounts.py +534 -0
- brawny/alerts/__init__.py +132 -0
- brawny/alerts/abi_resolver.py +530 -0
- brawny/alerts/base.py +152 -0
- brawny/alerts/context.py +271 -0
- brawny/alerts/contracts.py +635 -0
- brawny/alerts/encoded_call.py +201 -0
- brawny/alerts/errors.py +267 -0
- brawny/alerts/events.py +680 -0
- brawny/alerts/function_caller.py +364 -0
- brawny/alerts/health.py +185 -0
- brawny/alerts/routing.py +118 -0
- brawny/alerts/send.py +364 -0
- brawny/api.py +660 -0
- brawny/chain.py +93 -0
- brawny/cli/__init__.py +16 -0
- brawny/cli/app.py +17 -0
- brawny/cli/bootstrap.py +37 -0
- brawny/cli/commands/__init__.py +41 -0
- brawny/cli/commands/abi.py +93 -0
- brawny/cli/commands/accounts.py +632 -0
- brawny/cli/commands/console.py +495 -0
- brawny/cli/commands/contract.py +139 -0
- brawny/cli/commands/health.py +112 -0
- brawny/cli/commands/init_project.py +86 -0
- brawny/cli/commands/intents.py +130 -0
- brawny/cli/commands/job_dev.py +254 -0
- brawny/cli/commands/jobs.py +308 -0
- brawny/cli/commands/logs.py +87 -0
- brawny/cli/commands/maintenance.py +182 -0
- brawny/cli/commands/migrate.py +51 -0
- brawny/cli/commands/networks.py +253 -0
- brawny/cli/commands/run.py +249 -0
- brawny/cli/commands/script.py +209 -0
- brawny/cli/commands/signer.py +248 -0
- brawny/cli/helpers.py +265 -0
- brawny/cli_templates.py +1445 -0
- brawny/config/__init__.py +74 -0
- brawny/config/models.py +404 -0
- brawny/config/parser.py +633 -0
- brawny/config/routing.py +55 -0
- brawny/config/validation.py +246 -0
- brawny/daemon/__init__.py +14 -0
- brawny/daemon/context.py +69 -0
- brawny/daemon/core.py +702 -0
- brawny/daemon/loops.py +327 -0
- brawny/db/__init__.py +78 -0
- brawny/db/base.py +986 -0
- brawny/db/base_new.py +165 -0
- brawny/db/circuit_breaker.py +97 -0
- brawny/db/global_cache.py +298 -0
- brawny/db/mappers.py +182 -0
- brawny/db/migrate.py +349 -0
- brawny/db/migrations/001_init.sql +186 -0
- brawny/db/migrations/002_add_included_block.sql +7 -0
- brawny/db/migrations/003_add_broadcast_at.sql +10 -0
- brawny/db/migrations/004_broadcast_binding.sql +20 -0
- brawny/db/migrations/005_add_retry_after.sql +9 -0
- brawny/db/migrations/006_add_retry_count_column.sql +11 -0
- brawny/db/migrations/007_add_gap_tracking.sql +18 -0
- brawny/db/migrations/008_add_transactions.sql +72 -0
- brawny/db/migrations/009_add_intent_metadata.sql +5 -0
- brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
- brawny/db/migrations/011_add_job_logs.sql +24 -0
- brawny/db/migrations/012_add_claimed_by.sql +5 -0
- brawny/db/ops/__init__.py +29 -0
- brawny/db/ops/attempts.py +108 -0
- brawny/db/ops/blocks.py +83 -0
- brawny/db/ops/cache.py +93 -0
- brawny/db/ops/intents.py +296 -0
- brawny/db/ops/jobs.py +110 -0
- brawny/db/ops/logs.py +97 -0
- brawny/db/ops/nonces.py +322 -0
- brawny/db/postgres.py +2535 -0
- brawny/db/postgres_new.py +196 -0
- brawny/db/queries.py +584 -0
- brawny/db/sqlite.py +2733 -0
- brawny/db/sqlite_new.py +191 -0
- brawny/history.py +126 -0
- brawny/interfaces.py +136 -0
- brawny/invariants.py +155 -0
- brawny/jobs/__init__.py +26 -0
- brawny/jobs/base.py +287 -0
- brawny/jobs/discovery.py +233 -0
- brawny/jobs/job_validation.py +111 -0
- brawny/jobs/kv.py +125 -0
- brawny/jobs/registry.py +283 -0
- brawny/keystore.py +484 -0
- brawny/lifecycle.py +551 -0
- brawny/logging.py +290 -0
- brawny/metrics.py +594 -0
- brawny/model/__init__.py +53 -0
- brawny/model/contexts.py +319 -0
- brawny/model/enums.py +70 -0
- brawny/model/errors.py +194 -0
- brawny/model/events.py +93 -0
- brawny/model/startup.py +20 -0
- brawny/model/types.py +483 -0
- brawny/networks/__init__.py +96 -0
- brawny/networks/config.py +269 -0
- brawny/networks/manager.py +423 -0
- brawny/obs/__init__.py +67 -0
- brawny/obs/emit.py +158 -0
- brawny/obs/health.py +175 -0
- brawny/obs/heartbeat.py +133 -0
- brawny/reconciliation.py +108 -0
- brawny/scheduler/__init__.py +19 -0
- brawny/scheduler/poller.py +472 -0
- brawny/scheduler/reorg.py +632 -0
- brawny/scheduler/runner.py +708 -0
- brawny/scheduler/shutdown.py +371 -0
- brawny/script_tx.py +297 -0
- brawny/scripting.py +251 -0
- brawny/startup.py +76 -0
- brawny/telegram.py +393 -0
- brawny/testing.py +108 -0
- brawny/tx/__init__.py +41 -0
- brawny/tx/executor.py +1071 -0
- brawny/tx/fees.py +50 -0
- brawny/tx/intent.py +423 -0
- brawny/tx/monitor.py +628 -0
- brawny/tx/nonce.py +498 -0
- brawny/tx/replacement.py +456 -0
- brawny/tx/utils.py +26 -0
- brawny/utils.py +205 -0
- brawny/validation.py +69 -0
- brawny-0.1.13.dist-info/METADATA +156 -0
- brawny-0.1.13.dist-info/RECORD +141 -0
- brawny-0.1.13.dist-info/WHEEL +5 -0
- brawny-0.1.13.dist-info/entry_points.txt +2 -0
- brawny-0.1.13.dist-info/top_level.txt +1 -0
brawny/cli/helpers.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Shared helpers for CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import keyword
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def suppress_logging() -> None:
|
|
18
|
+
"""Suppress all logging output for CLI commands.
|
|
19
|
+
|
|
20
|
+
Must be called BEFORE importing any brawny modules that use logging.
|
|
21
|
+
"""
|
|
22
|
+
# Suppress stdlib logging
|
|
23
|
+
logging.disable(logging.CRITICAL)
|
|
24
|
+
|
|
25
|
+
# Configure structlog to drop all messages
|
|
26
|
+
import structlog
|
|
27
|
+
|
|
28
|
+
structlog.configure(
|
|
29
|
+
processors=[structlog.dev.ConsoleRenderer()],
|
|
30
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
31
|
+
context_class=dict,
|
|
32
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
33
|
+
cache_logger_on_first_use=False, # Don't cache so we can reconfigure later
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_config(config_path: str | None = None):
|
|
38
|
+
"""Get config, applying env overrides.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config_path: Path to config.yaml. If None, uses default lookup.
|
|
42
|
+
"""
|
|
43
|
+
from brawny.config import Config
|
|
44
|
+
from brawny.config import get_config as _get_config
|
|
45
|
+
|
|
46
|
+
if config_path:
|
|
47
|
+
config = Config.from_yaml(config_path)
|
|
48
|
+
config, _ = config.apply_env_overrides()
|
|
49
|
+
else:
|
|
50
|
+
config = _get_config()
|
|
51
|
+
|
|
52
|
+
return config
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_db(config_path: str | None = None):
|
|
56
|
+
"""Get database connection from config.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
config_path: Path to config.yaml. If None, uses get_config() default.
|
|
60
|
+
"""
|
|
61
|
+
from brawny.db import create_database
|
|
62
|
+
|
|
63
|
+
config = get_config(config_path)
|
|
64
|
+
|
|
65
|
+
db = create_database(
|
|
66
|
+
config.database_url,
|
|
67
|
+
pool_size=config.database_pool_size,
|
|
68
|
+
pool_max_overflow=config.database_pool_max_overflow,
|
|
69
|
+
pool_timeout=config.database_pool_timeout_seconds,
|
|
70
|
+
circuit_breaker_failures=config.db_circuit_breaker_failures,
|
|
71
|
+
circuit_breaker_seconds=config.db_circuit_breaker_seconds,
|
|
72
|
+
)
|
|
73
|
+
db.connect()
|
|
74
|
+
return db
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def discover_jobs_for_cli(config, extra_modules: tuple[str, ...] = ()):
|
|
78
|
+
"""Discover jobs using same logic as brawny start.
|
|
79
|
+
|
|
80
|
+
Priority: CLI modules > auto-discovery
|
|
81
|
+
|
|
82
|
+
Note: Call suppress_logging() BEFORE this function if you want silent discovery.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
config: Config instance
|
|
86
|
+
extra_modules: Additional modules from CLI flags
|
|
87
|
+
"""
|
|
88
|
+
from brawny.jobs.discovery import auto_discover_jobs, discover_jobs
|
|
89
|
+
|
|
90
|
+
if extra_modules:
|
|
91
|
+
discover_jobs(list(extra_modules))
|
|
92
|
+
else:
|
|
93
|
+
auto_discover_jobs()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def print_json(data: Any) -> None:
|
|
97
|
+
"""Print data as formatted JSON."""
|
|
98
|
+
click.echo(json.dumps(data, indent=2, default=str))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _validate_project_name(name: str) -> str:
|
|
102
|
+
"""Validate and normalize project name."""
|
|
103
|
+
if not name or not name.strip():
|
|
104
|
+
raise click.ClickException("Project name cannot be empty")
|
|
105
|
+
|
|
106
|
+
stripped = name.strip()
|
|
107
|
+
if len(stripped) > 100:
|
|
108
|
+
raise click.ClickException("Project name must be 100 characters or less")
|
|
109
|
+
if not stripped[0].isalpha():
|
|
110
|
+
raise click.ClickException("Project name must start with a letter")
|
|
111
|
+
if not re.match(r"^[A-Za-z0-9_-]+$", stripped):
|
|
112
|
+
raise click.ClickException(
|
|
113
|
+
"Project name contains invalid characters. Use only letters, numbers, hyphens, and underscores."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
package_name = stripped.lower().replace("-", "_")
|
|
117
|
+
if keyword.iskeyword(package_name):
|
|
118
|
+
raise click.ClickException(f"Project name '{name}' is a Python keyword")
|
|
119
|
+
|
|
120
|
+
stdlib_names = getattr(sys, "stdlib_module_names", set())
|
|
121
|
+
if package_name in stdlib_names:
|
|
122
|
+
raise click.ClickException(
|
|
123
|
+
f"Project name '{name}' conflicts with Python standard library"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return package_name
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _check_directory_empty(project_dir: Path) -> None:
|
|
130
|
+
"""Verify target directory is empty (or has only ignorable files)."""
|
|
131
|
+
if not project_dir.exists():
|
|
132
|
+
raise click.ClickException(f"Directory {project_dir} does not exist")
|
|
133
|
+
|
|
134
|
+
if not project_dir.is_dir():
|
|
135
|
+
raise click.ClickException(f"{project_dir} is not a directory")
|
|
136
|
+
|
|
137
|
+
if not os.access(project_dir, os.W_OK):
|
|
138
|
+
raise click.ClickException(f"Cannot write to {project_dir}: permission denied")
|
|
139
|
+
|
|
140
|
+
ignorable = {
|
|
141
|
+
".git", ".gitignore", "README.md", "LICENSE", ".DS_Store",
|
|
142
|
+
"venv", ".venv", "env", ".env",
|
|
143
|
+
".idea", ".vscode",
|
|
144
|
+
}
|
|
145
|
+
existing = set(p.name for p in project_dir.iterdir())
|
|
146
|
+
non_ignorable = existing - ignorable
|
|
147
|
+
|
|
148
|
+
if non_ignorable:
|
|
149
|
+
raise click.ClickException(
|
|
150
|
+
f"Directory is not empty. Found: {', '.join(sorted(non_ignorable))}\n"
|
|
151
|
+
"Run 'brawny init' in an empty directory."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _write_file(path: Path, content: str) -> None:
|
|
156
|
+
"""Write content to file, creating parent directories if needed."""
|
|
157
|
+
try:
|
|
158
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
path.write_text(content, encoding="utf-8")
|
|
160
|
+
except OSError as exc:
|
|
161
|
+
raise click.ClickException(f"Failed to create {path}: {exc}") from exc
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _write_pyproject(project_dir: Path, project_name: str, package_name: str) -> None:
|
|
165
|
+
from brawny.cli_templates import PYPROJECT_TEMPLATE
|
|
166
|
+
|
|
167
|
+
content = PYPROJECT_TEMPLATE.format(
|
|
168
|
+
project_name=project_name,
|
|
169
|
+
package_name=package_name,
|
|
170
|
+
)
|
|
171
|
+
_write_file(project_dir / "pyproject.toml", content)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _write_config(project_dir: Path, package_name: str) -> None:
|
|
175
|
+
from brawny.cli_templates import CONFIG_TEMPLATE
|
|
176
|
+
|
|
177
|
+
content = CONFIG_TEMPLATE.format(package_name=package_name)
|
|
178
|
+
_write_file(project_dir / "config.yaml", content)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _write_env_example(project_dir: Path) -> None:
|
|
182
|
+
from brawny.cli_templates import ENV_EXAMPLE_TEMPLATE
|
|
183
|
+
|
|
184
|
+
_write_file(project_dir / ".env.example", ENV_EXAMPLE_TEMPLATE)
|
|
185
|
+
|
|
186
|
+
def _write_agents(project_dir: Path) -> None:
|
|
187
|
+
from brawny.cli_templates import AGENTS_TEMPLATE
|
|
188
|
+
|
|
189
|
+
_write_file(project_dir / "AGENTS.md", AGENTS_TEMPLATE)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _write_gitignore(project_dir: Path) -> None:
|
|
193
|
+
from brawny.cli_templates import GITIGNORE_TEMPLATE
|
|
194
|
+
|
|
195
|
+
_write_file(project_dir / ".gitignore", GITIGNORE_TEMPLATE)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _write_jobs_init(jobs_dir: Path) -> None:
|
|
199
|
+
from brawny.cli_templates import INIT_JOBS_TEMPLATE
|
|
200
|
+
|
|
201
|
+
_write_file(jobs_dir / "__init__.py", INIT_JOBS_TEMPLATE)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _write_examples(path: Path) -> None:
|
|
205
|
+
from brawny.cli_templates import EXAMPLES_TEMPLATE
|
|
206
|
+
|
|
207
|
+
_write_file(path, EXAMPLES_TEMPLATE)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _write_monitoring(project_dir: Path) -> None:
|
|
211
|
+
from brawny.cli_templates import (
|
|
212
|
+
DOCKER_COMPOSE_MONITORING_TEMPLATE,
|
|
213
|
+
PROMETHEUS_CONFIG_TEMPLATE,
|
|
214
|
+
GRAFANA_DATASOURCE_TEMPLATE,
|
|
215
|
+
GRAFANA_DASHBOARDS_PROVIDER_TEMPLATE,
|
|
216
|
+
GRAFANA_DASHBOARD_TEMPLATE,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
monitoring_dir = project_dir / "monitoring"
|
|
220
|
+
_write_file(monitoring_dir / "docker-compose.yml", DOCKER_COMPOSE_MONITORING_TEMPLATE)
|
|
221
|
+
_write_file(monitoring_dir / "prometheus.yml", PROMETHEUS_CONFIG_TEMPLATE)
|
|
222
|
+
_write_file(
|
|
223
|
+
monitoring_dir / "grafana" / "provisioning" / "datasources" / "datasource.yml",
|
|
224
|
+
GRAFANA_DATASOURCE_TEMPLATE,
|
|
225
|
+
)
|
|
226
|
+
_write_file(
|
|
227
|
+
monitoring_dir / "grafana" / "provisioning" / "dashboards" / "dashboards.yml",
|
|
228
|
+
GRAFANA_DASHBOARDS_PROVIDER_TEMPLATE,
|
|
229
|
+
)
|
|
230
|
+
_write_file(
|
|
231
|
+
monitoring_dir / "grafana" / "provisioning" / "dashboards" / "brawny-overview.json",
|
|
232
|
+
GRAFANA_DASHBOARD_TEMPLATE,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _print_success(project_name: str, package_name: str) -> None:
|
|
237
|
+
"""Print success message with next steps."""
|
|
238
|
+
click.echo()
|
|
239
|
+
click.echo(click.style(f"Initialized {project_name}", fg="green", bold=True))
|
|
240
|
+
click.echo()
|
|
241
|
+
click.echo("Project structure:")
|
|
242
|
+
click.echo(" .")
|
|
243
|
+
click.echo(" ├── pyproject.toml")
|
|
244
|
+
click.echo(" ├── config.yaml")
|
|
245
|
+
click.echo(" ├── .env.example")
|
|
246
|
+
click.echo(" ├── .gitignore")
|
|
247
|
+
click.echo(f" ├── {package_name}/")
|
|
248
|
+
click.echo(" │ └── __init__.py")
|
|
249
|
+
click.echo(" ├── jobs/")
|
|
250
|
+
click.echo(" │ ├── __init__.py")
|
|
251
|
+
click.echo(" │ └── _examples.py")
|
|
252
|
+
click.echo(" ├── interfaces/")
|
|
253
|
+
click.echo(" ├── data/ # SQLite database")
|
|
254
|
+
click.echo(" └── monitoring/ # Prometheus + Grafana")
|
|
255
|
+
click.echo()
|
|
256
|
+
click.echo("Next steps:")
|
|
257
|
+
click.echo(click.style(" pip install -e .", fg="cyan"))
|
|
258
|
+
click.echo(click.style(" cp .env.example .env", fg="cyan"))
|
|
259
|
+
click.echo(" # Edit .env with your RPC URL and signer keys")
|
|
260
|
+
click.echo(click.style(" brawny run", fg="cyan"))
|
|
261
|
+
click.echo()
|
|
262
|
+
click.echo("To create your first job:")
|
|
263
|
+
click.echo(" 1. Copy a class from jobs/_examples.py")
|
|
264
|
+
click.echo(" 2. Create a new file in jobs/")
|
|
265
|
+
click.echo(" 3. Add @job decorator and customize")
|