pyth-pandas 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.
- pyth_pandas-0.1.0/PKG-INFO +213 -0
- pyth_pandas-0.1.0/README.md +169 -0
- pyth_pandas-0.1.0/explorer/__init__.py +0 -0
- pyth_pandas-0.1.0/explorer/app.py +14 -0
- pyth_pandas-0.1.0/explorer/home.py +30 -0
- pyth_pandas-0.1.0/explorer/pages/1_latest_prices.py +46 -0
- pyth_pandas-0.1.0/explorer/pages/2_guardian_set_upgrade.py +18 -0
- pyth_pandas-0.1.0/pyproject.toml +89 -0
- pyth_pandas-0.1.0/pyth_pandas/__init__.py +37 -0
- pyth_pandas-0.1.0/pyth_pandas/async_client.py +96 -0
- pyth_pandas-0.1.0/pyth_pandas/async_ws.py +137 -0
- pyth_pandas-0.1.0/pyth_pandas/client.py +217 -0
- pyth_pandas-0.1.0/pyth_pandas/exceptions.py +33 -0
- pyth_pandas-0.1.0/pyth_pandas/mcp_server.py +72 -0
- pyth_pandas-0.1.0/pyth_pandas/mixins/__init__.py +6 -0
- pyth_pandas-0.1.0/pyth_pandas/mixins/_governance.py +26 -0
- pyth_pandas-0.1.0/pyth_pandas/mixins/_prices.py +258 -0
- pyth_pandas-0.1.0/pyth_pandas/py.typed +0 -0
- pyth_pandas-0.1.0/pyth_pandas/schemas.py +53 -0
- pyth_pandas-0.1.0/pyth_pandas/types.py +77 -0
- pyth_pandas-0.1.0/pyth_pandas/utils.py +385 -0
- pyth_pandas-0.1.0/pyth_pandas/ws.py +191 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/PKG-INFO +213 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/SOURCES.txt +30 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/dependency_links.txt +1 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/entry_points.txt +3 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/requires.txt +28 -0
- pyth_pandas-0.1.0/pyth_pandas.egg-info/top_level.txt +2 -0
- pyth_pandas-0.1.0/setup.cfg +4 -0
- pyth_pandas-0.1.0/tests/test_async_unit.py +65 -0
- pyth_pandas-0.1.0/tests/test_integration.py +59 -0
- pyth_pandas-0.1.0/tests/test_unit.py +201 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyth-pandas
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pandas-style client for the Pyth Pro Router (Pyth Lazer) REST + WebSocket API
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/sigma-quantiphi/pyth-pandas
|
|
7
|
+
Project-URL: Documentation, https://sigma-quantiphi.github.io/pyth-pandas/
|
|
8
|
+
Project-URL: Issues, https://github.com/sigma-quantiphi/pyth-pandas/issues
|
|
9
|
+
Keywords: api-client,pandas,pyth,lazer,oracle,price-feeds
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: <3.14,>=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: cachetools>=5.3
|
|
21
|
+
Requires-Dist: httpx>=0.28
|
|
22
|
+
Requires-Dist: orjson>=3.10
|
|
23
|
+
Requires-Dist: pandas>=2.2
|
|
24
|
+
Requires-Dist: pandera>=0.22
|
|
25
|
+
Requires-Dist: tqdm>=4.66
|
|
26
|
+
Provides-Extra: ws
|
|
27
|
+
Requires-Dist: websocket-client>=1.8; extra == "ws"
|
|
28
|
+
Requires-Dist: websockets>=13.0; extra == "ws"
|
|
29
|
+
Provides-Extra: explorer
|
|
30
|
+
Requires-Dist: streamlit>=1.30; extra == "explorer"
|
|
31
|
+
Requires-Dist: plotly>=5.0; extra == "explorer"
|
|
32
|
+
Provides-Extra: mcp
|
|
33
|
+
Requires-Dist: fastmcp>=2.0; extra == "mcp"
|
|
34
|
+
Requires-Dist: tabulate>=0.9; extra == "mcp"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-httpx>=0.35; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.9; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
41
|
+
Requires-Dist: pandas-stubs>=2.2; extra == "dev"
|
|
42
|
+
Requires-Dist: types-cachetools>=5.3; extra == "dev"
|
|
43
|
+
Requires-Dist: python-dotenv>=1.0; extra == "dev"
|
|
44
|
+
|
|
45
|
+
# pyth-pandas
|
|
46
|
+
|
|
47
|
+
> pandas-style client for the Pyth Pro Router (Pyth Lazer) REST + WebSocket API
|
|
48
|
+
|
|
49
|
+
[](https://mybinder.org/v2/gh/sigma-quantiphi/pyth-pandas/HEAD?labpath=examples)
|
|
50
|
+
|
|
51
|
+
Every list endpoint returns a `pandas.DataFrame`, every dict endpoint returns
|
|
52
|
+
a `TypedDict`, and every shape is enforced by a `pandera` schema. Sync + async,
|
|
53
|
+
plus optional WebSocket, Streamlit explorer, and FastMCP server extras.
|
|
54
|
+
|
|
55
|
+
This client targets **Pyth Pro Router API v1.0.0** (per the published OpenAPI
|
|
56
|
+
spec). The package version (`0.1.0`) is independent of the upstream API
|
|
57
|
+
version — see the `API_VERSION` / `SUPPORTED_API_VERSIONS` class attributes
|
|
58
|
+
on `PythPandas`.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv pip install pyth-pandas # core
|
|
64
|
+
uv pip install "pyth-pandas[ws]" # + WebSocket
|
|
65
|
+
uv pip install "pyth-pandas[explorer]" # + Streamlit dashboard
|
|
66
|
+
uv pip install "pyth-pandas[mcp]" # + FastMCP server
|
|
67
|
+
uv pip install "pyth-pandas[dev]" # + test/lint toolchain
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quickstart
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from pyth_pandas import PythPandas
|
|
74
|
+
|
|
75
|
+
with PythPandas() as client:
|
|
76
|
+
df = client.fetch_latest_prices(
|
|
77
|
+
symbols=["Crypto.BTC/USD", "Crypto.ETH/USD"],
|
|
78
|
+
properties=["price", "confidence", "exponent", "publisherCount"],
|
|
79
|
+
formats=[],
|
|
80
|
+
)
|
|
81
|
+
df["humanPrice"] = df["price"] * 10 ** df["exponent"]
|
|
82
|
+
print(df[["priceFeedId", "humanPrice", "publisherCount"]])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The DataFrame has one row per feed. The on-chain payloads (when
|
|
86
|
+
requested via `formats=["evm", ...]`) and the update timestamp are
|
|
87
|
+
attached on `df.attrs`. For the raw `JsonUpdate` dict, use
|
|
88
|
+
`fetch_latest_prices_raw(...)`.
|
|
89
|
+
|
|
90
|
+
### Async
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import asyncio
|
|
94
|
+
from pyth_pandas import AsyncPythPandas
|
|
95
|
+
|
|
96
|
+
async def main():
|
|
97
|
+
async with AsyncPythPandas() as client:
|
|
98
|
+
df = await client.fetch_latest_prices(
|
|
99
|
+
symbols=["Crypto.BTC/USD"],
|
|
100
|
+
properties=["price", "exponent"],
|
|
101
|
+
formats=[],
|
|
102
|
+
)
|
|
103
|
+
print(df)
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
Pyth Pro requires a bearer access token. Set it via env var (recommended) or
|
|
111
|
+
constructor arg. Copy the shipped `.env.example` to `.env` and fill in your
|
|
112
|
+
key — examples and tests load it via `python-dotenv`:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cp .env.example .env
|
|
116
|
+
$EDITOR .env # set PYTH_API_KEY=...
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Or set it in your shell:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
export PYTH_API_KEY=...
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Or pass it directly:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
PythPandas(api_key="...")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Endpoints
|
|
132
|
+
|
|
133
|
+
| Method | HTTP | Returns |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `fetch_latest_prices(...)` | `POST /v1/latest_price` | `DataFrame[ParsedFeedSchema]` |
|
|
136
|
+
| `fetch_latest_prices_raw(...)` | `POST /v1/latest_price` | `JsonUpdate` (TypedDict) |
|
|
137
|
+
| `fetch_prices(timestamp=..., ...)` | `POST /v1/price` | `DataFrame[ParsedFeedSchema]` |
|
|
138
|
+
| `fetch_prices_raw(...)` | `POST /v1/price` | `JsonUpdate` |
|
|
139
|
+
| `reduce_price(payload=..., price_feed_ids=...)` | `POST /v1/reduce_price` | `DataFrame[ParsedFeedSchema]` |
|
|
140
|
+
| `reduce_price_raw(...)` | `POST /v1/reduce_price` | `JsonUpdate` |
|
|
141
|
+
| `get_guardian_set_upgrade()` | `GET /v1/guardian_set_upgrade` | `SignedGuardianSetUpgrade \| None` |
|
|
142
|
+
|
|
143
|
+
WebSocket: `PythWebSocket` / `AsyncPythWebSocket` (extra: `ws`) → connects
|
|
144
|
+
to `wss://pyth-lazer.dourolabs.app/v1/stream`.
|
|
145
|
+
|
|
146
|
+
## Compatibility
|
|
147
|
+
|
|
148
|
+
| pyth-pandas | Pyth Pro Router API |
|
|
149
|
+
|---|---|
|
|
150
|
+
| 0.1.x | 1.0.0 |
|
|
151
|
+
|
|
152
|
+
## Error handling
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from pyth_pandas import PythAuthError, PythRateLimitError, PythAPIError
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
df = client.fetch_latest_prices(symbols=["Crypto.BTC/USD"], properties=["price"])
|
|
159
|
+
except PythAuthError:
|
|
160
|
+
... # 401/403 or missing PYTH_API_KEY
|
|
161
|
+
except PythRateLimitError:
|
|
162
|
+
... # 429
|
|
163
|
+
except PythAPIError as e:
|
|
164
|
+
print(e.status_code, e.url, e.detail)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv pip install -e ".[dev,ws,explorer,mcp]"
|
|
171
|
+
uv run pytest tests/test_unit.py tests/test_async_unit.py -v
|
|
172
|
+
uv run ruff check pyth_pandas tests
|
|
173
|
+
uv run mypy pyth_pandas
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
CI runs the same checks on every push and PR via `.github/workflows/ci.yml`
|
|
177
|
+
across Python 3.11, 3.12, 3.13.
|
|
178
|
+
|
|
179
|
+
## Releasing
|
|
180
|
+
|
|
181
|
+
Releases are cut by pushing a `v*` tag. `.github/workflows/release.yml`:
|
|
182
|
+
|
|
183
|
+
1. Runs ruff + mypy + mocked pytest, then **live integration tests** against
|
|
184
|
+
the real upstream API. If the test job fails, build / publish / release
|
|
185
|
+
are all skipped.
|
|
186
|
+
2. Builds sdist + wheel via `uv build`.
|
|
187
|
+
3. Publishes to PyPI via [trusted publishing](https://docs.pypi.org/trusted-publishers/).
|
|
188
|
+
4. Creates a GitHub release.
|
|
189
|
+
|
|
190
|
+
**One-time setup:**
|
|
191
|
+
|
|
192
|
+
1. Reserve `pyth-pandas` on PyPI.
|
|
193
|
+
2. At <https://pypi.org/manage/account/publishing/>, add a pending publisher:
|
|
194
|
+
- Project: `pyth-pandas`
|
|
195
|
+
- Owner: your GitHub user/org
|
|
196
|
+
- Repo: `pyth-pandas`
|
|
197
|
+
- Workflow: `release.yml`
|
|
198
|
+
- Environment: `pypi`
|
|
199
|
+
3. Create the `pypi` environment in GitHub repo settings.
|
|
200
|
+
4. Add `PYTH_API_KEY` as an environment secret on the `pypi` environment so
|
|
201
|
+
live integration tests can run before publishing.
|
|
202
|
+
5. Enable GitHub Pages with source = "GitHub Actions" so `docs.yml` can
|
|
203
|
+
deploy the Sphinx site.
|
|
204
|
+
|
|
205
|
+
**To force a release if the test infra is broken:**
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
gh workflow run release.yml -f tag=v0.1.1 -f skip_tests=true
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Apache-2.0
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# pyth-pandas
|
|
2
|
+
|
|
3
|
+
> pandas-style client for the Pyth Pro Router (Pyth Lazer) REST + WebSocket API
|
|
4
|
+
|
|
5
|
+
[](https://mybinder.org/v2/gh/sigma-quantiphi/pyth-pandas/HEAD?labpath=examples)
|
|
6
|
+
|
|
7
|
+
Every list endpoint returns a `pandas.DataFrame`, every dict endpoint returns
|
|
8
|
+
a `TypedDict`, and every shape is enforced by a `pandera` schema. Sync + async,
|
|
9
|
+
plus optional WebSocket, Streamlit explorer, and FastMCP server extras.
|
|
10
|
+
|
|
11
|
+
This client targets **Pyth Pro Router API v1.0.0** (per the published OpenAPI
|
|
12
|
+
spec). The package version (`0.1.0`) is independent of the upstream API
|
|
13
|
+
version — see the `API_VERSION` / `SUPPORTED_API_VERSIONS` class attributes
|
|
14
|
+
on `PythPandas`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv pip install pyth-pandas # core
|
|
20
|
+
uv pip install "pyth-pandas[ws]" # + WebSocket
|
|
21
|
+
uv pip install "pyth-pandas[explorer]" # + Streamlit dashboard
|
|
22
|
+
uv pip install "pyth-pandas[mcp]" # + FastMCP server
|
|
23
|
+
uv pip install "pyth-pandas[dev]" # + test/lint toolchain
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pyth_pandas import PythPandas
|
|
30
|
+
|
|
31
|
+
with PythPandas() as client:
|
|
32
|
+
df = client.fetch_latest_prices(
|
|
33
|
+
symbols=["Crypto.BTC/USD", "Crypto.ETH/USD"],
|
|
34
|
+
properties=["price", "confidence", "exponent", "publisherCount"],
|
|
35
|
+
formats=[],
|
|
36
|
+
)
|
|
37
|
+
df["humanPrice"] = df["price"] * 10 ** df["exponent"]
|
|
38
|
+
print(df[["priceFeedId", "humanPrice", "publisherCount"]])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The DataFrame has one row per feed. The on-chain payloads (when
|
|
42
|
+
requested via `formats=["evm", ...]`) and the update timestamp are
|
|
43
|
+
attached on `df.attrs`. For the raw `JsonUpdate` dict, use
|
|
44
|
+
`fetch_latest_prices_raw(...)`.
|
|
45
|
+
|
|
46
|
+
### Async
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from pyth_pandas import AsyncPythPandas
|
|
51
|
+
|
|
52
|
+
async def main():
|
|
53
|
+
async with AsyncPythPandas() as client:
|
|
54
|
+
df = await client.fetch_latest_prices(
|
|
55
|
+
symbols=["Crypto.BTC/USD"],
|
|
56
|
+
properties=["price", "exponent"],
|
|
57
|
+
formats=[],
|
|
58
|
+
)
|
|
59
|
+
print(df)
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Authentication
|
|
65
|
+
|
|
66
|
+
Pyth Pro requires a bearer access token. Set it via env var (recommended) or
|
|
67
|
+
constructor arg. Copy the shipped `.env.example` to `.env` and fill in your
|
|
68
|
+
key — examples and tests load it via `python-dotenv`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cp .env.example .env
|
|
72
|
+
$EDITOR .env # set PYTH_API_KEY=...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or set it in your shell:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export PYTH_API_KEY=...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or pass it directly:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
PythPandas(api_key="...")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Endpoints
|
|
88
|
+
|
|
89
|
+
| Method | HTTP | Returns |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `fetch_latest_prices(...)` | `POST /v1/latest_price` | `DataFrame[ParsedFeedSchema]` |
|
|
92
|
+
| `fetch_latest_prices_raw(...)` | `POST /v1/latest_price` | `JsonUpdate` (TypedDict) |
|
|
93
|
+
| `fetch_prices(timestamp=..., ...)` | `POST /v1/price` | `DataFrame[ParsedFeedSchema]` |
|
|
94
|
+
| `fetch_prices_raw(...)` | `POST /v1/price` | `JsonUpdate` |
|
|
95
|
+
| `reduce_price(payload=..., price_feed_ids=...)` | `POST /v1/reduce_price` | `DataFrame[ParsedFeedSchema]` |
|
|
96
|
+
| `reduce_price_raw(...)` | `POST /v1/reduce_price` | `JsonUpdate` |
|
|
97
|
+
| `get_guardian_set_upgrade()` | `GET /v1/guardian_set_upgrade` | `SignedGuardianSetUpgrade \| None` |
|
|
98
|
+
|
|
99
|
+
WebSocket: `PythWebSocket` / `AsyncPythWebSocket` (extra: `ws`) → connects
|
|
100
|
+
to `wss://pyth-lazer.dourolabs.app/v1/stream`.
|
|
101
|
+
|
|
102
|
+
## Compatibility
|
|
103
|
+
|
|
104
|
+
| pyth-pandas | Pyth Pro Router API |
|
|
105
|
+
|---|---|
|
|
106
|
+
| 0.1.x | 1.0.0 |
|
|
107
|
+
|
|
108
|
+
## Error handling
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from pyth_pandas import PythAuthError, PythRateLimitError, PythAPIError
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
df = client.fetch_latest_prices(symbols=["Crypto.BTC/USD"], properties=["price"])
|
|
115
|
+
except PythAuthError:
|
|
116
|
+
... # 401/403 or missing PYTH_API_KEY
|
|
117
|
+
except PythRateLimitError:
|
|
118
|
+
... # 429
|
|
119
|
+
except PythAPIError as e:
|
|
120
|
+
print(e.status_code, e.url, e.detail)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv pip install -e ".[dev,ws,explorer,mcp]"
|
|
127
|
+
uv run pytest tests/test_unit.py tests/test_async_unit.py -v
|
|
128
|
+
uv run ruff check pyth_pandas tests
|
|
129
|
+
uv run mypy pyth_pandas
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
CI runs the same checks on every push and PR via `.github/workflows/ci.yml`
|
|
133
|
+
across Python 3.11, 3.12, 3.13.
|
|
134
|
+
|
|
135
|
+
## Releasing
|
|
136
|
+
|
|
137
|
+
Releases are cut by pushing a `v*` tag. `.github/workflows/release.yml`:
|
|
138
|
+
|
|
139
|
+
1. Runs ruff + mypy + mocked pytest, then **live integration tests** against
|
|
140
|
+
the real upstream API. If the test job fails, build / publish / release
|
|
141
|
+
are all skipped.
|
|
142
|
+
2. Builds sdist + wheel via `uv build`.
|
|
143
|
+
3. Publishes to PyPI via [trusted publishing](https://docs.pypi.org/trusted-publishers/).
|
|
144
|
+
4. Creates a GitHub release.
|
|
145
|
+
|
|
146
|
+
**One-time setup:**
|
|
147
|
+
|
|
148
|
+
1. Reserve `pyth-pandas` on PyPI.
|
|
149
|
+
2. At <https://pypi.org/manage/account/publishing/>, add a pending publisher:
|
|
150
|
+
- Project: `pyth-pandas`
|
|
151
|
+
- Owner: your GitHub user/org
|
|
152
|
+
- Repo: `pyth-pandas`
|
|
153
|
+
- Workflow: `release.yml`
|
|
154
|
+
- Environment: `pypi`
|
|
155
|
+
3. Create the `pypi` environment in GitHub repo settings.
|
|
156
|
+
4. Add `PYTH_API_KEY` as an environment secret on the `pypi` environment so
|
|
157
|
+
live integration tests can run before publishing.
|
|
158
|
+
5. Enable GitHub Pages with source = "GitHub Actions" so `docs.yml` can
|
|
159
|
+
deploy the Sphinx site.
|
|
160
|
+
|
|
161
|
+
**To force a release if the test infra is broken:**
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
gh workflow run release.yml -f tag=v0.1.1 -f skip_tests=true
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
Apache-2.0
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""CLI entry point for the pyth-pandas Streamlit explorer."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
home = Path(__file__).parent / "home.py"
|
|
10
|
+
os.execvp("streamlit", ["streamlit", "run", str(home), *sys.argv[1:]])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""pyth-pandas explorer — home page."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
from pyth_pandas import PythPandas
|
|
6
|
+
|
|
7
|
+
st.set_page_config(page_title="pyth-pandas explorer", layout="wide")
|
|
8
|
+
|
|
9
|
+
st.title("pyth-pandas explorer")
|
|
10
|
+
st.caption("Interactive playground for the Pyth Pro Router (Pyth Lazer) API")
|
|
11
|
+
|
|
12
|
+
with st.sidebar:
|
|
13
|
+
st.header("Client config")
|
|
14
|
+
base_url = st.text_input("Base URL", value="https://pyth-lazer.dourolabs.app/v1/")
|
|
15
|
+
api_key = st.text_input("PYTH_API_KEY", value="", type="password")
|
|
16
|
+
|
|
17
|
+
if "client" not in st.session_state or st.sidebar.button("Reload client"):
|
|
18
|
+
st.session_state.client = PythPandas(
|
|
19
|
+
base_url=base_url, api_key=api_key or None, use_tqdm=False
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
st.markdown(
|
|
23
|
+
"""
|
|
24
|
+
Pick an endpoint from the sidebar pages on the left.
|
|
25
|
+
|
|
26
|
+
- **Latest prices** — point-in-time snapshot per feed
|
|
27
|
+
- **Historical price** — feed values at a specific timestamp
|
|
28
|
+
- **Guardian set upgrade** — Wormhole governance status
|
|
29
|
+
"""
|
|
30
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Latest prices explorer page."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
st.title("Latest prices")
|
|
6
|
+
|
|
7
|
+
client = st.session_state.get("client")
|
|
8
|
+
if client is None:
|
|
9
|
+
st.warning("Configure the client on the home page first.")
|
|
10
|
+
st.stop()
|
|
11
|
+
|
|
12
|
+
symbols_text = st.text_input(
|
|
13
|
+
"Symbols (comma-separated)", value="Crypto.BTC/USD, Crypto.ETH/USD"
|
|
14
|
+
)
|
|
15
|
+
symbols = [s.strip() for s in symbols_text.split(",") if s.strip()] or None
|
|
16
|
+
|
|
17
|
+
properties = st.multiselect(
|
|
18
|
+
"Properties",
|
|
19
|
+
[
|
|
20
|
+
"price",
|
|
21
|
+
"confidence",
|
|
22
|
+
"exponent",
|
|
23
|
+
"publisherCount",
|
|
24
|
+
"bestBidPrice",
|
|
25
|
+
"bestAskPrice",
|
|
26
|
+
"emaPrice",
|
|
27
|
+
"emaConfidence",
|
|
28
|
+
"fundingRate",
|
|
29
|
+
"feedUpdateTimestamp",
|
|
30
|
+
],
|
|
31
|
+
default=["price", "confidence", "exponent", "publisherCount"],
|
|
32
|
+
)
|
|
33
|
+
channel = st.selectbox(
|
|
34
|
+
"Channel",
|
|
35
|
+
["real_time", "fixed_rate@50ms", "fixed_rate@200ms", "fixed_rate@1000ms"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if st.button("Fetch"):
|
|
39
|
+
with st.spinner("Fetching..."):
|
|
40
|
+
df = client.fetch_latest_prices(
|
|
41
|
+
symbols=symbols,
|
|
42
|
+
properties=properties,
|
|
43
|
+
channel=channel,
|
|
44
|
+
)
|
|
45
|
+
st.dataframe(df, use_container_width=True)
|
|
46
|
+
st.write("attrs:", df.attrs)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Guardian set upgrade status page."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
st.title("Guardian set upgrade")
|
|
6
|
+
|
|
7
|
+
client = st.session_state.get("client")
|
|
8
|
+
if client is None:
|
|
9
|
+
st.warning("Configure the client on the home page first.")
|
|
10
|
+
st.stop()
|
|
11
|
+
|
|
12
|
+
if st.button("Check"):
|
|
13
|
+
with st.spinner("Fetching..."):
|
|
14
|
+
result = client.get_guardian_set_upgrade()
|
|
15
|
+
if result is None:
|
|
16
|
+
st.success("No guardian set upgrade is currently in progress.")
|
|
17
|
+
else:
|
|
18
|
+
st.json(result)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyth-pandas"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "pandas-style client for the Pyth Pro Router (Pyth Lazer) REST + WebSocket API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11,<3.14"
|
|
7
|
+
license = "Apache-2.0"
|
|
8
|
+
keywords = ["api-client", "pandas", "pyth", "lazer", "oracle", "price-feeds"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"cachetools>=5.3",
|
|
22
|
+
"httpx>=0.28",
|
|
23
|
+
"orjson>=3.10",
|
|
24
|
+
"pandas>=2.2",
|
|
25
|
+
"pandera>=0.22",
|
|
26
|
+
"tqdm>=4.66",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
ws = [
|
|
31
|
+
"websocket-client>=1.8",
|
|
32
|
+
"websockets>=13.0",
|
|
33
|
+
]
|
|
34
|
+
explorer = [
|
|
35
|
+
"streamlit>=1.30",
|
|
36
|
+
"plotly>=5.0",
|
|
37
|
+
]
|
|
38
|
+
mcp = [
|
|
39
|
+
"fastmcp>=2.0",
|
|
40
|
+
"tabulate>=0.9",
|
|
41
|
+
]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8.0",
|
|
44
|
+
"pytest-httpx>=0.35",
|
|
45
|
+
"pytest-asyncio>=0.24",
|
|
46
|
+
"ruff>=0.9",
|
|
47
|
+
"mypy>=1.11",
|
|
48
|
+
"pandas-stubs>=2.2",
|
|
49
|
+
"types-cachetools>=5.3",
|
|
50
|
+
"python-dotenv>=1.0",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
pyth-pandas-explore = "explorer.app:main"
|
|
55
|
+
pyth-pandas-mcp = "pyth_pandas.mcp_server:main"
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://github.com/sigma-quantiphi/pyth-pandas"
|
|
59
|
+
Documentation = "https://sigma-quantiphi.github.io/pyth-pandas/"
|
|
60
|
+
Issues = "https://github.com/sigma-quantiphi/pyth-pandas/issues"
|
|
61
|
+
|
|
62
|
+
[build-system]
|
|
63
|
+
requires = ["setuptools>=70"]
|
|
64
|
+
build-backend = "setuptools.build_meta"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.packages.find]
|
|
67
|
+
include = ["pyth_pandas*", "explorer*"]
|
|
68
|
+
|
|
69
|
+
[tool.setuptools.package-data]
|
|
70
|
+
pyth_pandas = ["py.typed"]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
testpaths = ["tests"]
|
|
74
|
+
addopts = "-v"
|
|
75
|
+
asyncio_mode = "auto"
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
target-version = "py311"
|
|
79
|
+
line-length = 100
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
83
|
+
ignore = ["B008"]
|
|
84
|
+
|
|
85
|
+
[tool.mypy]
|
|
86
|
+
python_version = "3.11"
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
warn_return_any = false
|
|
89
|
+
warn_unused_ignores = true
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""pyth-pandas — pandas-style client for the Pyth Pro Router (Pyth Lazer) API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pyth_pandas.async_client import AsyncPythPandas
|
|
6
|
+
from pyth_pandas.client import PythPandas
|
|
7
|
+
from pyth_pandas.exceptions import (
|
|
8
|
+
PythAPIError,
|
|
9
|
+
PythAuthError,
|
|
10
|
+
PythError,
|
|
11
|
+
PythRateLimitError,
|
|
12
|
+
)
|
|
13
|
+
from pyth_pandas.schemas import ParsedFeedSchema
|
|
14
|
+
from pyth_pandas.types import (
|
|
15
|
+
JsonBinaryData,
|
|
16
|
+
JsonUpdate,
|
|
17
|
+
ParsedFeedPayload,
|
|
18
|
+
ParsedPayload,
|
|
19
|
+
SignedGuardianSetUpgrade,
|
|
20
|
+
SignedMerkleRoot,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AsyncPythPandas",
|
|
25
|
+
"JsonBinaryData",
|
|
26
|
+
"JsonUpdate",
|
|
27
|
+
"ParsedFeedPayload",
|
|
28
|
+
"ParsedFeedSchema",
|
|
29
|
+
"ParsedPayload",
|
|
30
|
+
"PythAPIError",
|
|
31
|
+
"PythAuthError",
|
|
32
|
+
"PythError",
|
|
33
|
+
"PythPandas",
|
|
34
|
+
"PythRateLimitError",
|
|
35
|
+
"SignedGuardianSetUpgrade",
|
|
36
|
+
"SignedMerkleRoot",
|
|
37
|
+
]
|