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.
- fastweb3-0.1.0/LICENSE +21 -0
- fastweb3-0.1.0/PKG-INFO +124 -0
- fastweb3-0.1.0/README.md +94 -0
- fastweb3-0.1.0/pyproject.toml +62 -0
- fastweb3-0.1.0/setup.cfg +4 -0
- fastweb3-0.1.0/src/fastweb3/__init__.py +12 -0
- fastweb3-0.1.0/src/fastweb3/deferred.py +216 -0
- fastweb3-0.1.0/src/fastweb3/endpoint.py +236 -0
- fastweb3-0.1.0/src/fastweb3/env.py +206 -0
- fastweb3-0.1.0/src/fastweb3/errors.py +75 -0
- fastweb3-0.1.0/src/fastweb3/formatters.py +105 -0
- fastweb3-0.1.0/src/fastweb3/middleware.py +233 -0
- fastweb3-0.1.0/src/fastweb3/provider/__init__.py +11 -0
- fastweb3-0.1.0/src/fastweb3/provider/endpoint_selection.py +286 -0
- fastweb3-0.1.0/src/fastweb3/provider/execution.py +714 -0
- fastweb3-0.1.0/src/fastweb3/provider/middleware.py +82 -0
- fastweb3-0.1.0/src/fastweb3/provider/pool.py +618 -0
- fastweb3-0.1.0/src/fastweb3/provider/provider.py +111 -0
- fastweb3-0.1.0/src/fastweb3/provider/rpc_error_retry.py +210 -0
- fastweb3-0.1.0/src/fastweb3/provider/types.py +94 -0
- fastweb3-0.1.0/src/fastweb3/transport/__init__.py +15 -0
- fastweb3-0.1.0/src/fastweb3/transport/base.py +40 -0
- fastweb3-0.1.0/src/fastweb3/transport/factory.py +53 -0
- fastweb3-0.1.0/src/fastweb3/transport/http.py +124 -0
- fastweb3-0.1.0/src/fastweb3/transport/ipc.py +188 -0
- fastweb3-0.1.0/src/fastweb3/transport/ws.py +283 -0
- fastweb3-0.1.0/src/fastweb3/utils.py +114 -0
- fastweb3-0.1.0/src/fastweb3/validation.py +294 -0
- fastweb3-0.1.0/src/fastweb3/web3/__init__.py +8 -0
- fastweb3-0.1.0/src/fastweb3/web3/batch.py +255 -0
- fastweb3-0.1.0/src/fastweb3/web3/eth.py +898 -0
- fastweb3-0.1.0/src/fastweb3/web3/web3.py +387 -0
- fastweb3-0.1.0/src/fastweb3.egg-info/PKG-INFO +124 -0
- fastweb3-0.1.0/src/fastweb3.egg-info/SOURCES.txt +35 -0
- fastweb3-0.1.0/src/fastweb3.egg-info/dependency_links.txt +1 -0
- fastweb3-0.1.0/src/fastweb3.egg-info/requires.txt +20 -0
- 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.
|
fastweb3-0.1.0/PKG-INFO
ADDED
|
@@ -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).
|
fastweb3-0.1.0/README.md
ADDED
|
@@ -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"]
|
fastweb3-0.1.0/setup.cfg
ADDED
|
@@ -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)
|