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.
Files changed (141) hide show
  1. brawny/__init__.py +106 -0
  2. brawny/_context.py +232 -0
  3. brawny/_rpc/__init__.py +38 -0
  4. brawny/_rpc/broadcast.py +172 -0
  5. brawny/_rpc/clients.py +98 -0
  6. brawny/_rpc/context.py +49 -0
  7. brawny/_rpc/errors.py +252 -0
  8. brawny/_rpc/gas.py +158 -0
  9. brawny/_rpc/manager.py +982 -0
  10. brawny/_rpc/selector.py +156 -0
  11. brawny/accounts.py +534 -0
  12. brawny/alerts/__init__.py +132 -0
  13. brawny/alerts/abi_resolver.py +530 -0
  14. brawny/alerts/base.py +152 -0
  15. brawny/alerts/context.py +271 -0
  16. brawny/alerts/contracts.py +635 -0
  17. brawny/alerts/encoded_call.py +201 -0
  18. brawny/alerts/errors.py +267 -0
  19. brawny/alerts/events.py +680 -0
  20. brawny/alerts/function_caller.py +364 -0
  21. brawny/alerts/health.py +185 -0
  22. brawny/alerts/routing.py +118 -0
  23. brawny/alerts/send.py +364 -0
  24. brawny/api.py +660 -0
  25. brawny/chain.py +93 -0
  26. brawny/cli/__init__.py +16 -0
  27. brawny/cli/app.py +17 -0
  28. brawny/cli/bootstrap.py +37 -0
  29. brawny/cli/commands/__init__.py +41 -0
  30. brawny/cli/commands/abi.py +93 -0
  31. brawny/cli/commands/accounts.py +632 -0
  32. brawny/cli/commands/console.py +495 -0
  33. brawny/cli/commands/contract.py +139 -0
  34. brawny/cli/commands/health.py +112 -0
  35. brawny/cli/commands/init_project.py +86 -0
  36. brawny/cli/commands/intents.py +130 -0
  37. brawny/cli/commands/job_dev.py +254 -0
  38. brawny/cli/commands/jobs.py +308 -0
  39. brawny/cli/commands/logs.py +87 -0
  40. brawny/cli/commands/maintenance.py +182 -0
  41. brawny/cli/commands/migrate.py +51 -0
  42. brawny/cli/commands/networks.py +253 -0
  43. brawny/cli/commands/run.py +249 -0
  44. brawny/cli/commands/script.py +209 -0
  45. brawny/cli/commands/signer.py +248 -0
  46. brawny/cli/helpers.py +265 -0
  47. brawny/cli_templates.py +1445 -0
  48. brawny/config/__init__.py +74 -0
  49. brawny/config/models.py +404 -0
  50. brawny/config/parser.py +633 -0
  51. brawny/config/routing.py +55 -0
  52. brawny/config/validation.py +246 -0
  53. brawny/daemon/__init__.py +14 -0
  54. brawny/daemon/context.py +69 -0
  55. brawny/daemon/core.py +702 -0
  56. brawny/daemon/loops.py +327 -0
  57. brawny/db/__init__.py +78 -0
  58. brawny/db/base.py +986 -0
  59. brawny/db/base_new.py +165 -0
  60. brawny/db/circuit_breaker.py +97 -0
  61. brawny/db/global_cache.py +298 -0
  62. brawny/db/mappers.py +182 -0
  63. brawny/db/migrate.py +349 -0
  64. brawny/db/migrations/001_init.sql +186 -0
  65. brawny/db/migrations/002_add_included_block.sql +7 -0
  66. brawny/db/migrations/003_add_broadcast_at.sql +10 -0
  67. brawny/db/migrations/004_broadcast_binding.sql +20 -0
  68. brawny/db/migrations/005_add_retry_after.sql +9 -0
  69. brawny/db/migrations/006_add_retry_count_column.sql +11 -0
  70. brawny/db/migrations/007_add_gap_tracking.sql +18 -0
  71. brawny/db/migrations/008_add_transactions.sql +72 -0
  72. brawny/db/migrations/009_add_intent_metadata.sql +5 -0
  73. brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
  74. brawny/db/migrations/011_add_job_logs.sql +24 -0
  75. brawny/db/migrations/012_add_claimed_by.sql +5 -0
  76. brawny/db/ops/__init__.py +29 -0
  77. brawny/db/ops/attempts.py +108 -0
  78. brawny/db/ops/blocks.py +83 -0
  79. brawny/db/ops/cache.py +93 -0
  80. brawny/db/ops/intents.py +296 -0
  81. brawny/db/ops/jobs.py +110 -0
  82. brawny/db/ops/logs.py +97 -0
  83. brawny/db/ops/nonces.py +322 -0
  84. brawny/db/postgres.py +2535 -0
  85. brawny/db/postgres_new.py +196 -0
  86. brawny/db/queries.py +584 -0
  87. brawny/db/sqlite.py +2733 -0
  88. brawny/db/sqlite_new.py +191 -0
  89. brawny/history.py +126 -0
  90. brawny/interfaces.py +136 -0
  91. brawny/invariants.py +155 -0
  92. brawny/jobs/__init__.py +26 -0
  93. brawny/jobs/base.py +287 -0
  94. brawny/jobs/discovery.py +233 -0
  95. brawny/jobs/job_validation.py +111 -0
  96. brawny/jobs/kv.py +125 -0
  97. brawny/jobs/registry.py +283 -0
  98. brawny/keystore.py +484 -0
  99. brawny/lifecycle.py +551 -0
  100. brawny/logging.py +290 -0
  101. brawny/metrics.py +594 -0
  102. brawny/model/__init__.py +53 -0
  103. brawny/model/contexts.py +319 -0
  104. brawny/model/enums.py +70 -0
  105. brawny/model/errors.py +194 -0
  106. brawny/model/events.py +93 -0
  107. brawny/model/startup.py +20 -0
  108. brawny/model/types.py +483 -0
  109. brawny/networks/__init__.py +96 -0
  110. brawny/networks/config.py +269 -0
  111. brawny/networks/manager.py +423 -0
  112. brawny/obs/__init__.py +67 -0
  113. brawny/obs/emit.py +158 -0
  114. brawny/obs/health.py +175 -0
  115. brawny/obs/heartbeat.py +133 -0
  116. brawny/reconciliation.py +108 -0
  117. brawny/scheduler/__init__.py +19 -0
  118. brawny/scheduler/poller.py +472 -0
  119. brawny/scheduler/reorg.py +632 -0
  120. brawny/scheduler/runner.py +708 -0
  121. brawny/scheduler/shutdown.py +371 -0
  122. brawny/script_tx.py +297 -0
  123. brawny/scripting.py +251 -0
  124. brawny/startup.py +76 -0
  125. brawny/telegram.py +393 -0
  126. brawny/testing.py +108 -0
  127. brawny/tx/__init__.py +41 -0
  128. brawny/tx/executor.py +1071 -0
  129. brawny/tx/fees.py +50 -0
  130. brawny/tx/intent.py +423 -0
  131. brawny/tx/monitor.py +628 -0
  132. brawny/tx/nonce.py +498 -0
  133. brawny/tx/replacement.py +456 -0
  134. brawny/tx/utils.py +26 -0
  135. brawny/utils.py +205 -0
  136. brawny/validation.py +69 -0
  137. brawny-0.1.13.dist-info/METADATA +156 -0
  138. brawny-0.1.13.dist-info/RECORD +141 -0
  139. brawny-0.1.13.dist-info/WHEEL +5 -0
  140. brawny-0.1.13.dist-info/entry_points.txt +2 -0
  141. brawny-0.1.13.dist-info/top_level.txt +1 -0
brawny/chain.py ADDED
@@ -0,0 +1,93 @@
1
+ """Chain information singleton.
2
+
3
+ Usage:
4
+ from brawny import chain
5
+
6
+ chain.height # Current block number
7
+ chain[-1] # Most recent block
8
+ chain[0] # Genesis block
9
+ chain.id # Chain ID
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from brawny._rpc.manager import RPCManager
18
+
19
+
20
+ _chain: "Chain | None" = None
21
+
22
+
23
+ class Chain:
24
+ """Brownie-compatible chain interface.
25
+
26
+ Provides access to block information and chain state.
27
+ """
28
+
29
+ def __init__(self, rpc: "RPCManager", chain_id: int) -> None:
30
+ self._rpc = rpc
31
+ self._chain_id = chain_id
32
+
33
+ @property
34
+ def height(self) -> int:
35
+ """Current block number (same as brownie's chain.height)."""
36
+ return self._rpc.get_block_number()
37
+
38
+ @property
39
+ def id(self) -> int:
40
+ """Chain ID."""
41
+ return self._chain_id
42
+
43
+ def __getitem__(self, index: int) -> dict[str, Any]:
44
+ """Get block by number. Supports negative indexing like brownie.
45
+
46
+ Example:
47
+ >>> chain[-1] # most recent block
48
+ >>> chain[0] # genesis block
49
+ """
50
+ if index < 0:
51
+ index = self.height + index + 1
52
+ return self._rpc.get_block(index)
53
+
54
+ def __repr__(self) -> str:
55
+ return f"<Chain id={self._chain_id} height={self.height}>"
56
+
57
+
58
+ def _init_chain(rpc: "RPCManager", chain_id: int) -> None:
59
+ """Initialize global chain singleton."""
60
+ global _chain
61
+ _chain = Chain(rpc, chain_id)
62
+
63
+
64
+ def _get_chain() -> Chain:
65
+ """Get chain singleton."""
66
+ if _chain is None:
67
+ raise RuntimeError(
68
+ "Chain not initialized. Run within script context."
69
+ )
70
+ return _chain
71
+
72
+
73
+ # Proxy for import-time access
74
+ class _ChainProxy:
75
+ """Proxy that delegates to chain singleton."""
76
+
77
+ @property
78
+ def height(self) -> int:
79
+ return _get_chain().height
80
+
81
+ @property
82
+ def id(self) -> int:
83
+ return _get_chain().id
84
+
85
+ def __getitem__(self, index: int) -> dict[str, Any]:
86
+ return _get_chain()[index]
87
+
88
+ def __repr__(self) -> str:
89
+ return repr(_get_chain())
90
+
91
+
92
+ # Global proxy instance
93
+ chain = _ChainProxy()
brawny/cli/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """CLI entrypoint for brawny."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from brawny.cli.app import main
6
+ from brawny.cli.commands import register_all
7
+
8
+
9
+ register_all(main)
10
+
11
+
12
+ __all__ = ["main"]
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
brawny/cli/app.py ADDED
@@ -0,0 +1,17 @@
1
+ """CLI entry group for brawny."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from brawny.cli.bootstrap import configure_bytecode_cache, load_env
8
+
9
+ load_env()
10
+ configure_bytecode_cache()
11
+
12
+
13
+ @click.group()
14
+ @click.version_option(version="0.1.0", prog_name="brawny")
15
+ def main() -> None:
16
+ """brawny: Block-driven Ethereum job/transaction execution framework."""
17
+ pass
@@ -0,0 +1,37 @@
1
+ """CLI bootstrap utilities."""
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from dotenv import load_dotenv
8
+
9
+
10
+ def load_env() -> None:
11
+ """Load environment variables from .env in the current directory."""
12
+ dotenv_path = Path.cwd() / ".env"
13
+ if dotenv_path.exists():
14
+ load_dotenv(dotenv_path)
15
+ else:
16
+ load_dotenv()
17
+
18
+
19
+ def configure_bytecode_cache() -> None:
20
+ """
21
+ Prevent __pycache__ from being created in the user's project by redirecting
22
+ pyc writes to a centralized cache directory.
23
+
24
+ Must run before importing/loading user job modules.
25
+ """
26
+ # Always disable bytecode writes to avoid __pycache__ in project jobs.
27
+ os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
28
+ sys.dont_write_bytecode = True
29
+
30
+ # If user already set a prefix, respect it.
31
+ if os.environ.get("PYTHONPYCACHEPREFIX"):
32
+ return
33
+
34
+ cache_dir = Path.home() / ".cache" / "brawny" / "pycache"
35
+ cache_dir.mkdir(parents=True, exist_ok=True)
36
+ os.environ["PYTHONPYCACHEPREFIX"] = str(cache_dir)
37
+ sys.pycache_prefix = str(cache_dir)
@@ -0,0 +1,41 @@
1
+ """CLI command registration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from brawny.cli.commands.abi import register as register_abi
6
+ from brawny.cli.commands.contract import register as register_contract
7
+ from brawny.cli.commands.health import register as register_health
8
+ from brawny.cli.commands.init_project import register as register_init
9
+ from brawny.cli.commands.intents import register as register_intents
10
+ from brawny.cli.commands.jobs import register as register_jobs
11
+ from brawny.cli.commands.logs import register as register_logs
12
+ from brawny.cli.commands.maintenance import register as register_maintenance
13
+ from brawny.cli.commands.migrate import register as register_migrate
14
+ from brawny.cli.commands.networks import register as register_networks
15
+ from brawny.cli.commands.run import register as register_start
16
+ from brawny.cli.commands.script import register as register_run
17
+ from brawny.cli.commands.accounts import register as register_accounts
18
+ from brawny.cli.commands.signer import register as register_signer
19
+
20
+
21
+ def register_all(main) -> None:
22
+ register_migrate(main)
23
+ register_start(main) # brawny start (daemon)
24
+ register_run(main) # brawny run (scripts, brownie-compatible)
25
+ register_init(main)
26
+ register_jobs(main) # includes jobs run
27
+ register_intents(main)
28
+ register_logs(main) # brawny logs list/cleanup
29
+ register_maintenance(main)
30
+ register_health(main)
31
+ register_abi(main)
32
+ register_contract(main)
33
+ register_accounts(main)
34
+ register_networks(main)
35
+ register_signer(main) # brawny signer force-reset, status
36
+ # Console has optional dependency (prompt_toolkit) - import lazily
37
+ try:
38
+ from brawny.cli.commands.console import register as register_console
39
+ register_console(main)
40
+ except ImportError:
41
+ pass # prompt_toolkit not installed
@@ -0,0 +1,93 @@
1
+ """ABI cache commands.
2
+
3
+ Uses global ABI cache at ~/.brawny/abi_cache.db.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import sys
10
+
11
+ import click
12
+
13
+ from brawny.db.global_cache import GlobalABICache
14
+
15
+
16
+ @click.group()
17
+ def abi() -> None:
18
+ """Manage ABI cache (stored in ~/.brawny/abi_cache.db)."""
19
+ pass
20
+
21
+
22
+ @abi.command("show")
23
+ @click.argument("address")
24
+ @click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
25
+ def abi_show(address: str, chain_id: int) -> None:
26
+ """Show cached ABI for address."""
27
+ cache = GlobalABICache()
28
+ cached = cache.get_cached_abi(chain_id, address)
29
+ if not cached:
30
+ click.echo(f"No cached ABI for {address} on chain {chain_id}.")
31
+ return
32
+
33
+ click.echo(f"\nCached ABI for {address}")
34
+ click.echo("-" * 60)
35
+ click.echo(f" Chain ID: {cached.chain_id}")
36
+ click.echo(f" Source: {cached.source}")
37
+ click.echo(f" Resolved: {cached.resolved_at}")
38
+ click.echo("\n ABI Preview:")
39
+
40
+ try:
41
+ abi_data = json.loads(cached.abi_json)
42
+ for item in abi_data[:10]:
43
+ if item.get("type") == "function":
44
+ name = item.get("name", "?")
45
+ inputs = ", ".join(i.get("type", "?") for i in item.get("inputs", []))
46
+ click.echo(f" - {name}({inputs})")
47
+ if len(abi_data) > 10:
48
+ click.echo(f" ... and {len(abi_data) - 10} more")
49
+ except json.JSONDecodeError:
50
+ click.echo(" (Invalid JSON)")
51
+ click.echo()
52
+
53
+
54
+ @abi.command("set")
55
+ @click.argument("address")
56
+ @click.option("--file", "abi_file", required=True, help="ABI JSON file")
57
+ @click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
58
+ def abi_set(address: str, abi_file: str, chain_id: int) -> None:
59
+ """Set cached ABI from file."""
60
+ from pathlib import Path
61
+
62
+ path = Path(abi_file)
63
+ if not path.exists():
64
+ click.echo(f"File not found: {abi_file}", err=True)
65
+ sys.exit(1)
66
+
67
+ abi_json = path.read_text()
68
+
69
+ try:
70
+ json.loads(abi_json)
71
+ except json.JSONDecodeError as e:
72
+ click.echo(f"Invalid JSON: {e}", err=True)
73
+ sys.exit(1)
74
+
75
+ cache = GlobalABICache()
76
+ cache.set_cached_abi(chain_id, address, abi_json, "manual")
77
+ click.echo(f"ABI cached for {address} on chain {chain_id}.")
78
+
79
+
80
+ @abi.command("clear")
81
+ @click.argument("address")
82
+ @click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
83
+ def abi_clear(address: str, chain_id: int) -> None:
84
+ """Clear cached ABI for address."""
85
+ cache = GlobalABICache()
86
+ if cache.clear_cached_abi(chain_id, address):
87
+ click.echo(f"ABI cache cleared for {address}.")
88
+ else:
89
+ click.echo(f"No cached ABI found for {address}.")
90
+
91
+
92
+ def register(main) -> None:
93
+ main.add_command(abi)