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
@@ -0,0 +1,196 @@
1
+ """PostgreSQL database implementation.
2
+
3
+ Slim execution layer with 4 primitives. All business operations live in db/ops/.
4
+ Uses connection pooling with psycopg3.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import threading
11
+ from contextlib import contextmanager
12
+ from typing import Any, Iterator, Literal
13
+
14
+ import psycopg
15
+ from psycopg.rows import dict_row
16
+ from psycopg_pool import ConnectionPool
17
+
18
+ from brawny.db.base_new import Database, Dialect, IsolationLevel
19
+ from brawny.model.errors import DatabaseError
20
+
21
+
22
+ def _rewrite(query: str) -> str:
23
+ """Rewrite :name placeholders to %(name)s for psycopg."""
24
+ # Match :word but not ::type_cast
25
+ return re.sub(r"(?<!:):(\w+)", r"%(\1)s", query)
26
+
27
+
28
+ class PostgresDatabase(Database):
29
+ """PostgreSQL implementation with connection pooling.
30
+
31
+ Uses psycopg3 with a synchronous connection pool.
32
+ Queries use :name placeholders (rewritten to %(name)s).
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ database_url: str,
38
+ pool_size: int = 5,
39
+ pool_max_overflow: int = 10,
40
+ pool_timeout: float = 30.0,
41
+ ) -> None:
42
+ """Initialize PostgreSQL database.
43
+
44
+ Args:
45
+ database_url: PostgreSQL connection URL
46
+ pool_size: Minimum pool connections
47
+ pool_max_overflow: Maximum additional connections
48
+ pool_timeout: Connection acquisition timeout
49
+ """
50
+ self._database_url = database_url
51
+ self._pool_size = pool_size
52
+ self._pool_max_size = pool_size + pool_max_overflow
53
+ self._pool_timeout = pool_timeout
54
+ self._pool: ConnectionPool | None = None
55
+ self._local = threading.local()
56
+
57
+ @property
58
+ def dialect(self) -> Dialect:
59
+ """Return dialect name for query selection."""
60
+ return "postgres"
61
+
62
+ def connect(self) -> None:
63
+ """Establish database connection pool."""
64
+ if self._pool is not None:
65
+ return
66
+ self._pool = ConnectionPool(
67
+ self._database_url,
68
+ min_size=self._pool_size,
69
+ max_size=self._pool_max_size,
70
+ timeout=self._pool_timeout,
71
+ )
72
+
73
+ def close(self) -> None:
74
+ """Close database connection pool."""
75
+ if self._pool:
76
+ self._pool.close()
77
+ self._pool = None
78
+
79
+ def is_connected(self) -> bool:
80
+ """Check if database is connected."""
81
+ return self._pool is not None
82
+
83
+ def _ensure_pool(self) -> ConnectionPool:
84
+ """Ensure pool exists and return it."""
85
+ if self._pool is None:
86
+ raise DatabaseError("Database not connected. Call connect() first.")
87
+ return self._pool
88
+
89
+ def _get_current_conn(self) -> psycopg.Connection | None:
90
+ """Get connection from current transaction context."""
91
+ return getattr(self._local, "conn", None)
92
+
93
+ @contextmanager
94
+ def transaction(
95
+ self, isolation_level: IsolationLevel | None = None
96
+ ) -> Iterator[None]:
97
+ """Context manager for database transactions.
98
+
99
+ Args:
100
+ isolation_level: Optional isolation level (e.g., SERIALIZABLE for nonce reservation)
101
+
102
+ Usage:
103
+ with db.transaction():
104
+ ops.intents.create_intent(db, ...)
105
+
106
+ with db.transaction(isolation_level="SERIALIZABLE"):
107
+ # Atomic nonce reservation
108
+ ...
109
+ """
110
+ if self._get_current_conn() is not None:
111
+ raise DatabaseError("Nested transactions are not supported")
112
+
113
+ pool = self._ensure_pool()
114
+ with pool.connection() as conn:
115
+ conn.row_factory = dict_row
116
+ with conn.transaction():
117
+ if isolation_level:
118
+ conn.execute(f"SET TRANSACTION ISOLATION LEVEL {isolation_level}")
119
+ self._local.conn = conn
120
+ try:
121
+ yield
122
+ finally:
123
+ self._local.conn = None
124
+
125
+ def execute(self, query: str, params: dict[str, Any] | None = None) -> None:
126
+ """Execute a query without returning results."""
127
+ query = _rewrite(query)
128
+ conn = self._get_current_conn()
129
+ try:
130
+ if conn is not None:
131
+ conn.execute(query, params or {})
132
+ return
133
+
134
+ pool = self._ensure_pool()
135
+ with pool.connection() as conn:
136
+ conn.row_factory = dict_row
137
+ with conn.transaction():
138
+ conn.execute(query, params or {})
139
+ except psycopg.Error as e:
140
+ raise DatabaseError(f"Postgres query failed: {e}") from e
141
+
142
+ def fetch_one(
143
+ self, query: str, params: dict[str, Any] | None = None
144
+ ) -> dict[str, Any] | None:
145
+ """Execute a query and return single result or None."""
146
+ query = _rewrite(query)
147
+ conn = self._get_current_conn()
148
+ try:
149
+ if conn is not None:
150
+ return conn.execute(query, params or {}).fetchone()
151
+
152
+ pool = self._ensure_pool()
153
+ with pool.connection() as conn:
154
+ conn.row_factory = dict_row
155
+ with conn.transaction():
156
+ return conn.execute(query, params or {}).fetchone()
157
+ except psycopg.Error as e:
158
+ raise DatabaseError(f"Postgres query failed: {e}") from e
159
+
160
+ def fetch_all(
161
+ self, query: str, params: dict[str, Any] | None = None
162
+ ) -> list[dict[str, Any]]:
163
+ """Execute a query and return all results."""
164
+ query = _rewrite(query)
165
+ conn = self._get_current_conn()
166
+ try:
167
+ if conn is not None:
168
+ return conn.execute(query, params or {}).fetchall()
169
+
170
+ pool = self._ensure_pool()
171
+ with pool.connection() as conn:
172
+ conn.row_factory = dict_row
173
+ with conn.transaction():
174
+ return conn.execute(query, params or {}).fetchall()
175
+ except psycopg.Error as e:
176
+ raise DatabaseError(f"Postgres query failed: {e}") from e
177
+
178
+ def execute_rowcount(
179
+ self, query: str, params: dict[str, Any] | None = None
180
+ ) -> int:
181
+ """Execute a query and return affected row count."""
182
+ query = _rewrite(query)
183
+ conn = self._get_current_conn()
184
+ try:
185
+ if conn is not None:
186
+ cur = conn.execute(query, params or {})
187
+ return cur.rowcount
188
+
189
+ pool = self._ensure_pool()
190
+ with pool.connection() as conn:
191
+ conn.row_factory = dict_row
192
+ with conn.transaction():
193
+ cur = conn.execute(query, params or {})
194
+ return cur.rowcount
195
+ except psycopg.Error as e:
196
+ raise DatabaseError(f"Postgres query failed: {e}") from e