fastweb3 0.1.0__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.
Files changed (37) hide show
  1. fastweb3-0.1.0/LICENSE +21 -0
  2. fastweb3-0.1.0/PKG-INFO +124 -0
  3. fastweb3-0.1.0/README.md +94 -0
  4. fastweb3-0.1.0/pyproject.toml +62 -0
  5. fastweb3-0.1.0/setup.cfg +4 -0
  6. fastweb3-0.1.0/src/fastweb3/__init__.py +12 -0
  7. fastweb3-0.1.0/src/fastweb3/deferred.py +216 -0
  8. fastweb3-0.1.0/src/fastweb3/endpoint.py +236 -0
  9. fastweb3-0.1.0/src/fastweb3/env.py +206 -0
  10. fastweb3-0.1.0/src/fastweb3/errors.py +75 -0
  11. fastweb3-0.1.0/src/fastweb3/formatters.py +105 -0
  12. fastweb3-0.1.0/src/fastweb3/middleware.py +233 -0
  13. fastweb3-0.1.0/src/fastweb3/provider/__init__.py +11 -0
  14. fastweb3-0.1.0/src/fastweb3/provider/endpoint_selection.py +286 -0
  15. fastweb3-0.1.0/src/fastweb3/provider/execution.py +714 -0
  16. fastweb3-0.1.0/src/fastweb3/provider/middleware.py +82 -0
  17. fastweb3-0.1.0/src/fastweb3/provider/pool.py +618 -0
  18. fastweb3-0.1.0/src/fastweb3/provider/provider.py +111 -0
  19. fastweb3-0.1.0/src/fastweb3/provider/rpc_error_retry.py +210 -0
  20. fastweb3-0.1.0/src/fastweb3/provider/types.py +94 -0
  21. fastweb3-0.1.0/src/fastweb3/transport/__init__.py +15 -0
  22. fastweb3-0.1.0/src/fastweb3/transport/base.py +40 -0
  23. fastweb3-0.1.0/src/fastweb3/transport/factory.py +53 -0
  24. fastweb3-0.1.0/src/fastweb3/transport/http.py +124 -0
  25. fastweb3-0.1.0/src/fastweb3/transport/ipc.py +188 -0
  26. fastweb3-0.1.0/src/fastweb3/transport/ws.py +283 -0
  27. fastweb3-0.1.0/src/fastweb3/utils.py +114 -0
  28. fastweb3-0.1.0/src/fastweb3/validation.py +294 -0
  29. fastweb3-0.1.0/src/fastweb3/web3/__init__.py +8 -0
  30. fastweb3-0.1.0/src/fastweb3/web3/batch.py +255 -0
  31. fastweb3-0.1.0/src/fastweb3/web3/eth.py +898 -0
  32. fastweb3-0.1.0/src/fastweb3/web3/web3.py +387 -0
  33. fastweb3-0.1.0/src/fastweb3.egg-info/PKG-INFO +124 -0
  34. fastweb3-0.1.0/src/fastweb3.egg-info/SOURCES.txt +35 -0
  35. fastweb3-0.1.0/src/fastweb3.egg-info/dependency_links.txt +1 -0
  36. fastweb3-0.1.0/src/fastweb3.egg-info/requires.txt +20 -0
  37. fastweb3-0.1.0/src/fastweb3.egg-info/top_level.txt +1 -0
fastweb3-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iamdefinitelyahuman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastweb3
3
+ Version: 0.1.0
4
+ Summary: High-performance Web3 client with batching and latency optimization.
5
+ Author: iamdefinitelyahuman
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/iamdefinitelyahuman/fastweb3
8
+ Project-URL: Repository, https://github.com/iamdefinitelyahuman/fastweb3
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: lazy-object-proxy>=1.12.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8; extra == "dev"
16
+ Requires-Dist: pytest-cov>=5; extra == "dev"
17
+ Requires-Dist: ruff>=0.9; extra == "dev"
18
+ Requires-Dist: pre-commit>=3; extra == "dev"
19
+ Requires-Dist: build; extra == "dev"
20
+ Requires-Dist: twine; extra == "dev"
21
+ Requires-Dist: mkdocs>=1.6; extra == "dev"
22
+ Requires-Dist: mkdocs-material>=9.5; extra == "dev"
23
+ Requires-Dist: websocket-client>=1.6; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
26
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
27
+ Provides-Extra: wss
28
+ Requires-Dist: websocket-client>=1.6; extra == "wss"
29
+ Dynamic: license-file
30
+
31
+ # fastweb3
32
+
33
+ High-performance Web3 client.
34
+
35
+ **NOTE**: This library is still in early alpha development. Prior to a `v1.0.0` (which may never come), expect breaking changes and no backward compatibility between versions.
36
+
37
+ ## Installation
38
+
39
+ You can install the latest release via `pip`:
40
+
41
+ ```bash
42
+ pip install fastweb3
43
+ ```
44
+
45
+ Or clone the repository for the most up-to-date version:
46
+
47
+ ```bash
48
+ git clone https://github.com/iamdefinitelyahuman/fastweb3.git
49
+ cd fastweb3
50
+ pip install -e .
51
+ ```
52
+
53
+ ## Features
54
+
55
+ ### Automatic public RPC discovery and pooling
56
+
57
+ `fastweb3` queries a [list of public RPC endpoints](https://github.com/ethereum-lists/chains), maintains a pool of useable nodes, distributes requests between them, and reroutes failed requests. Users can rely on public infrastructure without thinking about timeouts, rate limiting, nodes falling out of sync, etc.
58
+
59
+ ```py
60
+ >>> from fastweb3 import Web3
61
+
62
+ # only a chainId is needed to connect
63
+ >>> w3 = Web3(1)
64
+
65
+ # the object immediately begins querying endpoints to check availability and latency
66
+ # the best nodes are selected to be used in an "active pool"
67
+ >>> w3.active_pool_size()
68
+ 6
69
+
70
+ # we continue to monitor all known good endpoints, in case we have issues with any
71
+ # member of the active pool
72
+ >>> w3.pool_capacity()
73
+ 11
74
+ ```
75
+
76
+ Users with their own RPC can target it as their primary endpoint. This endpoint is then favored for write methods, but read methods continue to be distributed amongst the node pool.
77
+
78
+ ```py
79
+ >>> w3 = Web3(1, primary_endpoint=["my.local.node"])
80
+ ```
81
+
82
+ ### Deferred Execution
83
+
84
+ RPC calls return [`Proxy`](github.com/ionelmc/python-lazy-object-proxy) objects immediately, while network I/O happens in the background.
85
+
86
+ ```py
87
+ >>> amount = w3.eth.get_balance('0xd8da6bf26964af9d7eed9e03e53415d37aa96045')
88
+ >>> amount
89
+ <Proxy at 0x7b9ac0764a40 of object ... >
90
+ >>> print(amount)
91
+ 32131215082101779377
92
+ ```
93
+
94
+ ### Simple Batching Semantics
95
+
96
+ Batching uses natural syntax. With a context manager open, each requests is queued in the same batch until one of the `Proxy` objects is read, at which point the entire request is processed. This also guarantees queried values are all read from the same block.
97
+
98
+ ```py
99
+ >>> with w3.batch_requests():
100
+ ... a = w3.eth.get_balance("0xd8da6bf26964af9d7eed9e03e53415d37aa96045")
101
+ ... b = w3.eth.get_balance("0x1db3439a222c519ab44bb1144fc28167b4fa6ee6")
102
+ ... # both eth_getBalance queries are sent in the same batched request
103
+ ... print(a + b)
104
+ ...
105
+ 32840401623804415458
106
+ ```
107
+
108
+ ## Tests
109
+
110
+ First, install the dev dependencies:
111
+
112
+ ```bash
113
+ pip install -e ".[dev]"
114
+ ```
115
+
116
+ To run the test suite:
117
+
118
+ ```bash
119
+ pytest
120
+ ```
121
+
122
+ ## License
123
+
124
+ This project is licensed under the [MIT license](LICENSE).
@@ -0,0 +1,94 @@
1
+ # fastweb3
2
+
3
+ High-performance Web3 client.
4
+
5
+ **NOTE**: This library is still in early alpha development. Prior to a `v1.0.0` (which may never come), expect breaking changes and no backward compatibility between versions.
6
+
7
+ ## Installation
8
+
9
+ You can install the latest release via `pip`:
10
+
11
+ ```bash
12
+ pip install fastweb3
13
+ ```
14
+
15
+ Or clone the repository for the most up-to-date version:
16
+
17
+ ```bash
18
+ git clone https://github.com/iamdefinitelyahuman/fastweb3.git
19
+ cd fastweb3
20
+ pip install -e .
21
+ ```
22
+
23
+ ## Features
24
+
25
+ ### Automatic public RPC discovery and pooling
26
+
27
+ `fastweb3` queries a [list of public RPC endpoints](https://github.com/ethereum-lists/chains), maintains a pool of useable nodes, distributes requests between them, and reroutes failed requests. Users can rely on public infrastructure without thinking about timeouts, rate limiting, nodes falling out of sync, etc.
28
+
29
+ ```py
30
+ >>> from fastweb3 import Web3
31
+
32
+ # only a chainId is needed to connect
33
+ >>> w3 = Web3(1)
34
+
35
+ # the object immediately begins querying endpoints to check availability and latency
36
+ # the best nodes are selected to be used in an "active pool"
37
+ >>> w3.active_pool_size()
38
+ 6
39
+
40
+ # we continue to monitor all known good endpoints, in case we have issues with any
41
+ # member of the active pool
42
+ >>> w3.pool_capacity()
43
+ 11
44
+ ```
45
+
46
+ Users with their own RPC can target it as their primary endpoint. This endpoint is then favored for write methods, but read methods continue to be distributed amongst the node pool.
47
+
48
+ ```py
49
+ >>> w3 = Web3(1, primary_endpoint=["my.local.node"])
50
+ ```
51
+
52
+ ### Deferred Execution
53
+
54
+ RPC calls return [`Proxy`](github.com/ionelmc/python-lazy-object-proxy) objects immediately, while network I/O happens in the background.
55
+
56
+ ```py
57
+ >>> amount = w3.eth.get_balance('0xd8da6bf26964af9d7eed9e03e53415d37aa96045')
58
+ >>> amount
59
+ <Proxy at 0x7b9ac0764a40 of object ... >
60
+ >>> print(amount)
61
+ 32131215082101779377
62
+ ```
63
+
64
+ ### Simple Batching Semantics
65
+
66
+ Batching uses natural syntax. With a context manager open, each requests is queued in the same batch until one of the `Proxy` objects is read, at which point the entire request is processed. This also guarantees queried values are all read from the same block.
67
+
68
+ ```py
69
+ >>> with w3.batch_requests():
70
+ ... a = w3.eth.get_balance("0xd8da6bf26964af9d7eed9e03e53415d37aa96045")
71
+ ... b = w3.eth.get_balance("0x1db3439a222c519ab44bb1144fc28167b4fa6ee6")
72
+ ... # both eth_getBalance queries are sent in the same batched request
73
+ ... print(a + b)
74
+ ...
75
+ 32840401623804415458
76
+ ```
77
+
78
+ ## Tests
79
+
80
+ First, install the dev dependencies:
81
+
82
+ ```bash
83
+ pip install -e ".[dev]"
84
+ ```
85
+
86
+ To run the test suite:
87
+
88
+ ```bash
89
+ pytest
90
+ ```
91
+
92
+ ## License
93
+
94
+ This project is licensed under the [MIT license](LICENSE).
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fastweb3"
7
+ version = "0.1.0"
8
+ description = "High-performance Web3 client with batching and latency optimization."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "iamdefinitelyahuman" }
14
+ ]
15
+
16
+ dependencies = [
17
+ "httpx>=0.27",
18
+ "lazy-object-proxy>=1.12.0",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "pytest>=8",
24
+ "pytest-cov>=5",
25
+ "ruff>=0.9",
26
+ "pre-commit>=3",
27
+ "build",
28
+ "twine",
29
+ "mkdocs>=1.6",
30
+ "mkdocs-material>=9.5",
31
+ "websocket-client>=1.6",
32
+ ]
33
+ docs = [
34
+ "mkdocs>=1.6",
35
+ "mkdocs-material>=9.5",
36
+ ]
37
+ wss = ["websocket-client>=1.6"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/iamdefinitelyahuman/fastweb3"
41
+ Repository = "https://github.com/iamdefinitelyahuman/fastweb3"
42
+
43
+ [tool.setuptools]
44
+ package-dir = {"" = "src"}
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.ruff]
50
+ line-length = 100
51
+ target-version = "py312"
52
+
53
+ [tool.ruff.lint]
54
+ select = ["E", "F", "I"]
55
+
56
+ [tool.pytest.ini_options]
57
+ addopts = [
58
+ "--cov=fastweb3",
59
+ "--cov-report=term-missing",
60
+ "--cov-report=xml",
61
+ ]
62
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ """fastweb3 - High-performance Web3 client."""
2
+
3
+ from .errors import FastWeb3Error, RPCError, TransportError
4
+ from .web3 import Web3, Web3Config
5
+
6
+ __all__ = [
7
+ "Web3",
8
+ "Web3Config",
9
+ "FastWeb3Error",
10
+ "RPCError",
11
+ "TransportError",
12
+ ]
@@ -0,0 +1,216 @@
1
+ """Deferred values used for optional background execution.
2
+
3
+ This module provides a small primitive for returning proxy objects whose value
4
+ is computed later.
5
+
6
+ The main entrypoint is `deferred_response()`, which returns a
7
+ ``lazy_object_proxy.Proxy`` that blocks on first access until the underlying
8
+ value is ready.
9
+
10
+ These utilities are primarily used internally (e.g., for batched JSON-RPC
11
+ requests), but are designed to be safe under concurrent access.
12
+ """
13
+
14
+ # src/fastweb3/deferred.py
15
+ import threading
16
+ import traceback
17
+ from typing import Any, Callable, Optional
18
+
19
+ from lazy_object_proxy import Proxy
20
+
21
+ _UNSET = object()
22
+
23
+
24
+ class Handle:
25
+ """Handle for a deferred value.
26
+
27
+ A `Handle` represents a value that will be produced later, either by
28
+ a background task or by a "ref" callback that runs on first access.
29
+
30
+ The handle stores either a final value or an exception. Consumers typically
31
+ do not use `Handle` directly; instead, use `deferred_response()`
32
+ which returns a proxy object backed by a handle.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ bg_func: Optional[Callable[["Handle"], None]] = None,
38
+ format_func: Optional[Callable[[Any], Any]] = None,
39
+ ref_func: Optional[Callable[["Handle"], None]] = None,
40
+ ) -> None:
41
+ """Create a new handle.
42
+
43
+ Exactly one of ``bg_func`` or ``ref_func`` must be provided.
44
+
45
+ Args:
46
+ bg_func: If provided, executed immediately in a background thread.
47
+ The function is responsible for eventually calling
48
+ `set_value()` or `set_exc()`.
49
+ format_func: Optional pure function applied to the raw value before
50
+ storing it. If ``format_func`` raises, the handle is marked as
51
+ failed and the exception is re-raised.
52
+ ref_func: If provided, executed at most once on first access of the
53
+ proxy *and only if* the value has not been set yet. This is
54
+ typically used to force a flush or otherwise ensure that a value
55
+ will be produced.
56
+
57
+ Raises:
58
+ ValueError: If neither ``bg_func`` nor ``ref_func`` is provided.
59
+ """
60
+ self.lock = threading.Lock()
61
+ self.event = threading.Event()
62
+
63
+ self._exc: Optional[BaseException] = None
64
+ self._value: Any = _UNSET
65
+
66
+ self._format_func = format_func
67
+ self._ref_func = ref_func
68
+ self._ref_ran = False
69
+
70
+ # Capture creation site (strip this __init__ frame)
71
+ self._created_stack = traceback.format_stack()[:-1]
72
+
73
+ if bg_func is not None:
74
+
75
+ def execute_in_background() -> None:
76
+ try:
77
+ bg_func(self)
78
+ except BaseException as exc:
79
+ self.set_exc(exc)
80
+ finally:
81
+ self.event.set()
82
+
83
+ threading.Thread(target=execute_in_background, daemon=True).start()
84
+ else:
85
+ if ref_func is None:
86
+ raise ValueError("Must set one of bg_func, ref_func")
87
+ self.event.set()
88
+
89
+ def set_exc(self, exc: BaseException) -> None:
90
+ """Mark the handle as failed.
91
+
92
+ Args:
93
+ exc: The exception to store.
94
+
95
+ Notes:
96
+ The exception is annotated with a creation-site note the first time
97
+ it is stored.
98
+ """
99
+ self._add_creation_note(exc)
100
+ with self.lock:
101
+ if self._exc is None:
102
+ self._exc = exc
103
+
104
+ def set_value(self, raw_value: Any) -> None:
105
+ """Store the handle's final value.
106
+
107
+ Args:
108
+ raw_value: Raw value to store. If ``format_func`` was provided,
109
+ it is applied first.
110
+
111
+ Raises:
112
+ BaseException: Re-raises any exception produced by ``format_func``.
113
+ Exception: If the value was already set.
114
+ """
115
+ with self.lock:
116
+ if self._exc is not None:
117
+ raise self._exc
118
+ if self._value is not _UNSET:
119
+ raise Exception("Value already set")
120
+ format_func = self._format_func
121
+
122
+ try:
123
+ final_value = raw_value if format_func is None else format_func(raw_value)
124
+ except BaseException as exc:
125
+ self.set_exc(exc)
126
+ raise
127
+
128
+ with self.lock:
129
+ if self._exc is not None:
130
+ raise self._exc
131
+ if self._value is not _UNSET:
132
+ raise Exception("Value already set")
133
+ self._value = final_value
134
+
135
+ def _add_creation_note(self, exc: BaseException) -> None:
136
+ # Add exactly once per exception instance
137
+ if getattr(exc, "_fastweb3_creation_note", False):
138
+ return
139
+ setattr(exc, "_fastweb3_creation_note", True)
140
+
141
+ exc.add_note(
142
+ "Deferred value was created at (most recent call last):\n"
143
+ + "".join(self._created_stack)
144
+ )
145
+
146
+ def get_value(self) -> Any:
147
+ """Return the stored value, blocking until it is available.
148
+
149
+ Returns:
150
+ The resolved value.
151
+
152
+ Raises:
153
+ BaseException: If the handle resolved to an exception.
154
+ AttributeError: If the handle was resolved but no value was set.
155
+ """
156
+ self.event.wait()
157
+
158
+ # Fast-path: already resolved or already failed
159
+ with self.lock:
160
+ exc = self._exc
161
+ value = self._value
162
+ if exc is not None:
163
+ raise exc
164
+ if value is not _UNSET:
165
+ return value
166
+
167
+ # Decide whether to run ref (at most once)
168
+ if self._ref_func is None or self._ref_ran:
169
+ ref = None
170
+ else:
171
+ self._ref_ran = True
172
+ ref = self._ref_func
173
+
174
+ if ref is not None:
175
+ try:
176
+ ref(self)
177
+ except BaseException as exc:
178
+ self.set_exc(exc)
179
+ raise
180
+
181
+ # Re-check after ref
182
+ with self.lock:
183
+ exc = self._exc
184
+ value = self._value
185
+
186
+ if exc is not None:
187
+ raise exc
188
+
189
+ if value is _UNSET:
190
+ e = AttributeError("Deferred value was not set")
191
+ self._add_creation_note(e)
192
+ raise e
193
+
194
+ return value
195
+
196
+
197
+ def deferred_response(
198
+ bg_func: Callable[[Handle], None],
199
+ *,
200
+ format_func: Optional[Callable[[Any], Any]] = None,
201
+ ref_func: Optional[Callable[[Handle], None]] = None,
202
+ ) -> Any:
203
+ """Return a proxy whose value is computed later.
204
+
205
+ Args:
206
+ bg_func: Background function that computes the value and calls
207
+ `Handle.set_value()` or `Handle.set_exc()`.
208
+ format_func: Optional formatter applied to the raw value.
209
+ ref_func: Optional callback that runs on first proxy access if the
210
+ value is not yet set.
211
+
212
+ Returns:
213
+ A ``lazy_object_proxy.Proxy`` that resolves to the final value.
214
+ """
215
+ h = Handle(bg_func, format_func=format_func, ref_func=ref_func)
216
+ return Proxy(h.get_value)