bitvavo-api-upgraded 4.1.1__py3-none-any.whl → 4.1.2__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.
- bitvavo_api_upgraded/dataframe_utils.py +1 -1
- {bitvavo_api_upgraded-4.1.1.dist-info → bitvavo_api_upgraded-4.1.2.dist-info}/METADATA +5 -4
- {bitvavo_api_upgraded-4.1.1.dist-info → bitvavo_api_upgraded-4.1.2.dist-info}/RECORD +8 -8
- bitvavo_client/adapters/returns_adapter.py +3 -4
- bitvavo_client/core/model_preferences.py +20 -2
- bitvavo_client/endpoints/private.py +172 -30
- bitvavo_client/endpoints/public.py +102 -12
- {bitvavo_api_upgraded-4.1.1.dist-info → bitvavo_api_upgraded-4.1.2.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: bitvavo-api-upgraded
|
3
|
-
Version: 4.1.
|
3
|
+
Version: 4.1.2
|
4
4
|
Summary: A unit-tested fork of the Bitvavo API
|
5
5
|
Author: Bitvavo BV (original code), NostraDavid
|
6
6
|
Author-email: NostraDavid <55331731+NostraDavid@users.noreply.github.com>
|
@@ -301,7 +301,7 @@ client = BitvavoClient(preferred_model=ModelPreference.PYDANTIC)
|
|
301
301
|
result = client.public.time() # Returns: ServerTime(time=1609459200000)
|
302
302
|
|
303
303
|
# Option 3: DataFrame format (pandas, polars, etc.)
|
304
|
-
client = BitvavoClient(preferred_model=ModelPreference.
|
304
|
+
client = BitvavoClient(preferred_model=ModelPreference.POLARS)
|
305
305
|
result = client.public.markets() # Returns: polars.DataFrame with market data
|
306
306
|
```
|
307
307
|
|
@@ -314,9 +314,10 @@ client = BitvavoClient(preferred_model=ModelPreference.RAW)
|
|
314
314
|
# Get raw dict (uses default)
|
315
315
|
raw_data = client.public.markets()
|
316
316
|
|
317
|
-
# Override to get DataFrame for this request
|
317
|
+
# Override to get Polars DataFrame for this request
|
318
318
|
import polars as pl
|
319
|
-
|
319
|
+
from bitvavo_client.core.model_preferences import ModelPreference
|
320
|
+
df_data = client.public.markets(model=ModelPreference.POLARS)
|
320
321
|
|
321
322
|
# Override to get Pydantic model
|
322
323
|
from bitvavo_client.core.public_models import Markets
|
@@ -1,19 +1,19 @@
|
|
1
1
|
bitvavo_api_upgraded/__init__.py,sha256=J_HdGBmZOfb1eOydaxsPmXfOIZ58hVa1qAfE6QErUHs,301
|
2
2
|
bitvavo_api_upgraded/bitvavo.py,sha256=_3FRVVPg7_1HrALyGPjcuokCsHF5oz6itN_GKx7yTMo,166155
|
3
|
-
bitvavo_api_upgraded/dataframe_utils.py,sha256=
|
3
|
+
bitvavo_api_upgraded/dataframe_utils.py,sha256=nGNHVhaCtnmwRYcZXoy4d5LREHeTDYxPmrixB08NcF4,5811
|
4
4
|
bitvavo_api_upgraded/helper_funcs.py,sha256=4oBdQ1xB-C2XkQTmN-refzIzWfO-IUowDSWhOSFdCRU,3212
|
5
5
|
bitvavo_api_upgraded/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
bitvavo_api_upgraded/settings.py,sha256=I1fogU6_kb1hOe_0YDzOgDhzKfnnYFoIR2OXbwtyD4E,5291
|
7
7
|
bitvavo_api_upgraded/type_aliases.py,sha256=SbPBcuKWJZPZ8DSDK-Uycu5O-TUO6ejVaTt_7oyGyIU,1979
|
8
8
|
bitvavo_client/__init__.py,sha256=YXTBdP6fBREV34VeTqS_gkjfzIoHv5uSYhbqSUEeAVU,207
|
9
9
|
bitvavo_client/adapters/__init__.py,sha256=9YVjMhNiAN6K1x7N0UvAXMQwuhOFy4W6Edrba1hW8KI,64
|
10
|
-
bitvavo_client/adapters/returns_adapter.py,sha256=
|
10
|
+
bitvavo_client/adapters/returns_adapter.py,sha256=3HSAPw6HB9GCS8AbKmeidURpZXnvMZqkvalOu6JhBv0,14195
|
11
11
|
bitvavo_client/auth/__init__.py,sha256=bjWu5WCKNNnNoLcVU290tKBml9M5afmcxaU_KrkisSQ,39
|
12
12
|
bitvavo_client/auth/rate_limit.py,sha256=09IjdeTU9GZlszFBmiM6RynE50NwIICyy-8oM6HtX-k,3229
|
13
13
|
bitvavo_client/auth/signing.py,sha256=DJrI1R1SLKjl276opj9hN4RrKIgsMhxsSEDA8b7T04I,1037
|
14
14
|
bitvavo_client/core/__init__.py,sha256=WqjaU9Ut5JdZwn4tsR1vDdrSfMjEJred3im6fvWpalc,39
|
15
15
|
bitvavo_client/core/errors.py,sha256=jWHHQKqkkhpHS9TeKlccl7wuyuRrq0H_PGZ0bl6sbW4,460
|
16
|
-
bitvavo_client/core/model_preferences.py,sha256=
|
16
|
+
bitvavo_client/core/model_preferences.py,sha256=uPXjAD3B4UaBwzmhSN7k59oG71RGmV05y6-FGDKM184,1134
|
17
17
|
bitvavo_client/core/private_models.py,sha256=lttKQJQ6sVVwgFJ7WsnXim185KZUfjjuuQzsciPuEL8,33232
|
18
18
|
bitvavo_client/core/public_models.py,sha256=st1m1yOxutPhVQteJoRS72mzlQNeL7ynYjG1hqYg27w,37492
|
19
19
|
bitvavo_client/core/settings.py,sha256=H721f_EpMFrvk77SQLC3IOHN-UJecSX6uGlgV527PdA,2237
|
@@ -23,8 +23,8 @@ bitvavo_client/df/__init__.py,sha256=1ui3dsRhDvy0NOoOi4zj5gR48u828Au9K_qtH9S1hIo
|
|
23
23
|
bitvavo_client/df/convert.py,sha256=bf46QYmyB8o4KFdwUOfaxLV3_Qp29gq0L6hk3Qj-0pI,2522
|
24
24
|
bitvavo_client/endpoints/__init__.py,sha256=X1e_Hn6xN7FBBLgKOpjdfIbKiXSp8f4gYSn8wRcn4ro,43
|
25
25
|
bitvavo_client/endpoints/common.py,sha256=fc4gNNZ2zGMJJwkbHexNz6qMDLjl6dalQFGDXWmBo2E,2413
|
26
|
-
bitvavo_client/endpoints/private.py,sha256=
|
27
|
-
bitvavo_client/endpoints/public.py,sha256=
|
26
|
+
bitvavo_client/endpoints/private.py,sha256=dVpDegr_o7-Lsq4HXgPGLas2DcVB8CndfXy7GqHK8AQ,48101
|
27
|
+
bitvavo_client/endpoints/public.py,sha256=gDMv1_4RxDCPgY8_VJIQu_9T9PTgS-xIHpWuT0CYLfI,29591
|
28
28
|
bitvavo_client/facade.py,sha256=LZELLda4x--6AUAgrloTgWIWTmt_iVMMcSkEp8xf2P4,2644
|
29
29
|
bitvavo_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
30
|
bitvavo_client/schemas/__init__.py,sha256=udqMyAFElrcBbNJIhoarQuTI-CF425JvPFqgLVjILU8,1126
|
@@ -33,6 +33,6 @@ bitvavo_client/schemas/public_schemas.py,sha256=zfV6C_PQvNLLYEWS72ZD77Nm3XtRrEgh
|
|
33
33
|
bitvavo_client/transport/__init__.py,sha256=H7txnyuz6v84_GzdBiqpsehVQitEymgUTA5AJPeUEvg,44
|
34
34
|
bitvavo_client/transport/http.py,sha256=fJX-OfLIox-UOvwuUztPcu95whXAmkfFdo8cQoYdfK0,5614
|
35
35
|
bitvavo_client/ws/__init__.py,sha256=Q4SVEq3EihXLVUKpguMdxrhfNAoU8cncpMFbU6kIX_0,44
|
36
|
-
bitvavo_api_upgraded-4.1.
|
37
|
-
bitvavo_api_upgraded-4.1.
|
38
|
-
bitvavo_api_upgraded-4.1.
|
36
|
+
bitvavo_api_upgraded-4.1.2.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
|
37
|
+
bitvavo_api_upgraded-4.1.2.dist-info/METADATA,sha256=ToW1wOCtabb5dLJDkQSHPDbVFuAM36jb3f14RQb00a4,35937
|
38
|
+
bitvavo_api_upgraded-4.1.2.dist-info/RECORD,,
|
@@ -285,13 +285,12 @@ def decode_response_result( # noqa: C901 (complexity)
|
|
285
285
|
# I don't like the complexity of this piece, but it's needed because the data from ticker_book may return an
|
286
286
|
# int when it should be a float... Why is their DB such a damned mess? Fuck me, man...
|
287
287
|
try:
|
288
|
-
|
289
|
-
|
290
|
-
if model is pl.DataFrame:
|
288
|
+
# Check if model is a polars DataFrame specifically by checking module and class name
|
289
|
+
if hasattr(model, "__name__") and "polars" in str(model.__module__) and "DataFrame" in str(model):
|
291
290
|
parsed = model(data, schema=schema, strict=False) # type: ignore[arg-type]
|
292
291
|
else:
|
293
292
|
parsed = model(data, schema=schema) # type: ignore[arg-type]
|
294
|
-
except ImportError:
|
293
|
+
except (ImportError, AttributeError):
|
295
294
|
parsed = model(data, schema=schema) # type: ignore[arg-type]
|
296
295
|
return Success(parsed)
|
297
296
|
except Exception as exc: # noqa: BLE001
|
@@ -23,8 +23,26 @@ class ModelPreference(StrEnum):
|
|
23
23
|
# Return raw Python data structures (dict/list)
|
24
24
|
RAW = "raw"
|
25
25
|
|
26
|
-
# Return Polars DataFrame
|
27
|
-
|
26
|
+
# Return Polars DataFrame
|
27
|
+
POLARS = "polars"
|
28
|
+
|
29
|
+
# Return Pandas DataFrame
|
30
|
+
PANDAS = "pandas"
|
31
|
+
|
32
|
+
# Return PyArrow Table
|
33
|
+
PYARROW = "pyarrow"
|
34
|
+
|
35
|
+
# Return Dask DataFrame
|
36
|
+
DASK = "dask"
|
37
|
+
|
38
|
+
# Return Modin DataFrame
|
39
|
+
MODIN = "modin"
|
40
|
+
|
41
|
+
# Return CuDF DataFrame (GPU accelerated)
|
42
|
+
CUDF = "cudf"
|
43
|
+
|
44
|
+
# Return Ibis DataFrame
|
45
|
+
IBIS = "ibis"
|
28
46
|
|
29
47
|
# Return appropriate Pydantic model for each endpoint
|
30
48
|
PYDANTIC = "pydantic"
|
@@ -6,7 +6,6 @@ import contextlib
|
|
6
6
|
from typing import TYPE_CHECKING, Any, TypeVar
|
7
7
|
|
8
8
|
import httpx
|
9
|
-
import polars as pl
|
10
9
|
from returns.result import Failure, Result, Success
|
11
10
|
|
12
11
|
from bitvavo_client.adapters.returns_adapter import BitvavoError
|
@@ -53,13 +52,80 @@ def _extract_dataframe_data(data: Any, *, items_key: str | None = None) -> list[
|
|
53
52
|
raise ValueError(msg)
|
54
53
|
|
55
54
|
|
55
|
+
# DataFrames preference to library mapping
|
56
|
+
_DATAFRAME_LIBRARY_MAP = {
|
57
|
+
ModelPreference.POLARS: ("polars", "pl.DataFrame"),
|
58
|
+
ModelPreference.PANDAS: ("pandas", "pd.DataFrame"),
|
59
|
+
ModelPreference.PYARROW: ("pyarrow", "pa.Table"),
|
60
|
+
ModelPreference.DASK: ("dask", "dd.DataFrame"),
|
61
|
+
ModelPreference.MODIN: ("modin", "mpd.DataFrame"),
|
62
|
+
ModelPreference.CUDF: ("cudf", "cudf.DataFrame"),
|
63
|
+
ModelPreference.IBIS: ("ibis", "ibis.Table"),
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
def _get_dataframe_constructor(preference: ModelPreference) -> tuple[Any, str]:
|
68
|
+
"""Get the appropriate dataframe constructor and library name based on preference.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
preference: ModelPreference enum value
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Tuple of (constructor_class, library_name)
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
ImportError: If required library is not available
|
78
|
+
ValueError: If preference is not a supported dataframe type
|
79
|
+
"""
|
80
|
+
if preference not in _DATAFRAME_LIBRARY_MAP:
|
81
|
+
msg = f"Unsupported dataframe preference: {preference}"
|
82
|
+
raise ValueError(msg)
|
83
|
+
|
84
|
+
library_name, _ = _DATAFRAME_LIBRARY_MAP[preference]
|
85
|
+
|
86
|
+
try:
|
87
|
+
if preference == ModelPreference.POLARS:
|
88
|
+
import polars as pl # noqa: PLC0415
|
89
|
+
|
90
|
+
return pl.DataFrame, library_name
|
91
|
+
if preference == ModelPreference.PANDAS:
|
92
|
+
import pandas as pd # noqa: PLC0415
|
93
|
+
|
94
|
+
return pd.DataFrame, library_name
|
95
|
+
if preference == ModelPreference.PYARROW:
|
96
|
+
import pyarrow as pa # noqa: PLC0415
|
97
|
+
|
98
|
+
return pa.Table.from_pylist, library_name
|
99
|
+
if preference == ModelPreference.DASK:
|
100
|
+
import dask.dataframe as dd # noqa: PLC0415
|
101
|
+
import pandas as pd # noqa: PLC0415
|
102
|
+
|
103
|
+
return lambda data, **kwargs: dd.from_pandas(pd.DataFrame(data), npartitions=1), library_name
|
104
|
+
if preference == ModelPreference.MODIN:
|
105
|
+
import modin.pandas as mpd # noqa: PLC0415
|
106
|
+
|
107
|
+
return mpd.DataFrame, library_name
|
108
|
+
if preference == ModelPreference.CUDF:
|
109
|
+
import cudf # noqa: PLC0415
|
110
|
+
|
111
|
+
return cudf.DataFrame, library_name
|
112
|
+
# ModelPreference.IBIS
|
113
|
+
import ibis # noqa: PLC0415
|
114
|
+
|
115
|
+
return lambda data, **kwargs: ibis.memtable(data), library_name
|
116
|
+
except ImportError as e:
|
117
|
+
msg = f"{library_name} is not installed. Install with appropriate package manager."
|
118
|
+
raise ImportError(msg) from e
|
119
|
+
|
120
|
+
|
56
121
|
def _create_dataframe_from_data(
|
57
|
-
data: Any, *, items_key: str | None = None, empty_schema: dict[str, Any] | None = None
|
58
|
-
) -> Result[
|
59
|
-
"""Create a DataFrame from API response data.
|
122
|
+
data: Any, preference: ModelPreference, *, items_key: str | None = None, empty_schema: dict[str, Any] | None = None
|
123
|
+
) -> Result[Any, BitvavoError]:
|
124
|
+
"""Create a DataFrame from API response data using the specified preference.
|
60
125
|
|
61
126
|
Args:
|
62
127
|
data: Raw API response data
|
128
|
+
preference: ModelPreference enum value indicating which library to use
|
63
129
|
items_key: Key to extract from nested response (optional)
|
64
130
|
empty_schema: Schema to use for empty DataFrames and type casting
|
65
131
|
|
@@ -68,28 +134,18 @@ def _create_dataframe_from_data(
|
|
68
134
|
"""
|
69
135
|
try:
|
70
136
|
df_data = _extract_dataframe_data(data, items_key=items_key)
|
137
|
+
constructor, library_name = _get_dataframe_constructor(preference)
|
71
138
|
|
72
139
|
if df_data:
|
73
|
-
# Create DataFrame
|
74
|
-
df =
|
75
|
-
|
76
|
-
# Apply schema casting if provided
|
77
|
-
if empty_schema:
|
78
|
-
# Cast columns that exist in both DataFrame and schema
|
79
|
-
for col, expected_dtype in empty_schema.items():
|
80
|
-
if col in df.columns:
|
81
|
-
with contextlib.suppress(pl.exceptions.PolarsError, ValueError):
|
82
|
-
df = df.with_columns(pl.col(col).cast(expected_dtype))
|
83
|
-
|
140
|
+
# Create DataFrame using appropriate constructor
|
141
|
+
df = _create_dataframe_with_constructor(constructor, library_name, df_data, empty_schema)
|
84
142
|
return Success(df) # type: ignore[return-value]
|
85
143
|
|
86
|
-
# Create empty DataFrame
|
87
|
-
|
88
|
-
empty_schema = {"id": pl.String} # Minimal default schema
|
89
|
-
df = pl.DataFrame([], schema=empty_schema)
|
144
|
+
# Create empty DataFrame
|
145
|
+
df = _create_empty_dataframe(constructor, library_name, empty_schema)
|
90
146
|
return Success(df) # type: ignore[return-value]
|
91
147
|
|
92
|
-
except (ValueError, TypeError,
|
148
|
+
except (ValueError, TypeError, ImportError) as exc:
|
93
149
|
error = BitvavoError(
|
94
150
|
http_status=500,
|
95
151
|
error_code=-1,
|
@@ -100,6 +156,71 @@ def _create_dataframe_from_data(
|
|
100
156
|
return Failure(error)
|
101
157
|
|
102
158
|
|
159
|
+
def _create_dataframe_with_constructor(
|
160
|
+
constructor: Any, library_name: str, df_data: list | dict, empty_schema: dict | None
|
161
|
+
) -> Any:
|
162
|
+
"""Create DataFrame with data using the appropriate constructor."""
|
163
|
+
if library_name == "polars":
|
164
|
+
df = constructor(df_data, strict=False)
|
165
|
+
if empty_schema:
|
166
|
+
df = _apply_polars_schema(df, empty_schema)
|
167
|
+
return df
|
168
|
+
if library_name in ("pandas", "modin", "cudf"):
|
169
|
+
df = constructor(df_data)
|
170
|
+
if empty_schema:
|
171
|
+
df = _apply_pandas_like_schema(df, empty_schema)
|
172
|
+
return df
|
173
|
+
if library_name == "pyarrow" or library_name == "dask":
|
174
|
+
return constructor(df_data)
|
175
|
+
# ibis
|
176
|
+
return constructor(df_data)
|
177
|
+
|
178
|
+
|
179
|
+
def _create_empty_dataframe(constructor: Any, library_name: str, empty_schema: dict | None) -> Any:
|
180
|
+
"""Create empty DataFrame using the appropriate constructor."""
|
181
|
+
if empty_schema is None:
|
182
|
+
empty_schema = {"id": str} if library_name != "polars" else {"id": "pl.String"}
|
183
|
+
|
184
|
+
if library_name == "polars":
|
185
|
+
import polars as pl # noqa: PLC0415
|
186
|
+
|
187
|
+
schema = {k: pl.String if v == "pl.String" else v for k, v in empty_schema.items()}
|
188
|
+
return constructor([], schema=schema)
|
189
|
+
if library_name in ("pandas", "modin", "cudf", "dask") or library_name == "pyarrow":
|
190
|
+
return constructor([])
|
191
|
+
# ibis
|
192
|
+
return constructor([dict.fromkeys(empty_schema.keys())])
|
193
|
+
|
194
|
+
|
195
|
+
def _apply_polars_schema(df: Any, schema: dict) -> Any:
|
196
|
+
"""Apply schema to polars DataFrame."""
|
197
|
+
import polars as pl # noqa: PLC0415
|
198
|
+
|
199
|
+
for col, expected_dtype in schema.items():
|
200
|
+
if col in df.columns:
|
201
|
+
with contextlib.suppress(Exception):
|
202
|
+
df = df.with_columns(pl.col(col).cast(expected_dtype))
|
203
|
+
return df
|
204
|
+
|
205
|
+
|
206
|
+
def _apply_pandas_like_schema(df: Any, schema: dict) -> Any:
|
207
|
+
"""Apply schema to pandas-like DataFrame."""
|
208
|
+
pandas_schema = {}
|
209
|
+
for col, dtype in schema.items():
|
210
|
+
if col in df.columns:
|
211
|
+
if "String" in str(dtype) or "Utf8" in str(dtype):
|
212
|
+
pandas_schema[col] = "string"
|
213
|
+
elif "Int" in str(dtype):
|
214
|
+
pandas_schema[col] = "int64"
|
215
|
+
elif "Float" in str(dtype):
|
216
|
+
pandas_schema[col] = "float64"
|
217
|
+
|
218
|
+
if pandas_schema and hasattr(df, "astype"):
|
219
|
+
with contextlib.suppress(Exception):
|
220
|
+
df = df.astype(pandas_schema)
|
221
|
+
return df
|
222
|
+
|
223
|
+
|
103
224
|
class PrivateAPI:
|
104
225
|
"""Handles all private Bitvavo API endpoints requiring authentication."""
|
105
226
|
|
@@ -118,7 +239,22 @@ class PrivateAPI:
|
|
118
239
|
default_schema: Default schema for DataFrame conversion
|
119
240
|
"""
|
120
241
|
self.http: HTTPClient = http_client
|
121
|
-
|
242
|
+
|
243
|
+
# Handle preferred_model parameter - try to convert strings to ModelPreference,
|
244
|
+
# but allow arbitrary strings to pass through for custom handling
|
245
|
+
if preferred_model is None:
|
246
|
+
self.preferred_model = None
|
247
|
+
elif isinstance(preferred_model, ModelPreference):
|
248
|
+
self.preferred_model = preferred_model
|
249
|
+
elif isinstance(preferred_model, str):
|
250
|
+
try:
|
251
|
+
self.preferred_model = ModelPreference(preferred_model)
|
252
|
+
except ValueError:
|
253
|
+
# If string doesn't match a valid ModelPreference, store as-is
|
254
|
+
self.preferred_model = preferred_model
|
255
|
+
else:
|
256
|
+
self.preferred_model = preferred_model
|
257
|
+
|
122
258
|
self.default_schema = default_schema
|
123
259
|
|
124
260
|
def _get_effective_model(
|
@@ -152,7 +288,8 @@ class PrivateAPI:
|
|
152
288
|
if self.preferred_model == ModelPreference.RAW:
|
153
289
|
return Any, schema
|
154
290
|
|
155
|
-
|
291
|
+
# Handle all DataFrame preferences
|
292
|
+
if self.preferred_model in _DATAFRAME_LIBRARY_MAP:
|
156
293
|
# Validate endpoint type
|
157
294
|
if endpoint_type not in list(DEFAULT_SCHEMAS.keys()):
|
158
295
|
msg = f"Invalid endpoint_type '{endpoint_type}'. Valid types: {sorted(DEFAULT_SCHEMAS)}"
|
@@ -162,7 +299,8 @@ class PrivateAPI:
|
|
162
299
|
# Convert to dict if it's a Mapping but not already a dict
|
163
300
|
if effective_schema is not None and not isinstance(effective_schema, dict):
|
164
301
|
effective_schema = dict(effective_schema)
|
165
|
-
|
302
|
+
# Return the preference itself, not a specific DataFrame class
|
303
|
+
return self.preferred_model, effective_schema
|
166
304
|
|
167
305
|
if self.preferred_model == ModelPreference.PYDANTIC:
|
168
306
|
# Map endpoint types to appropriate Pydantic models
|
@@ -224,8 +362,10 @@ class PrivateAPI:
|
|
224
362
|
raw_data = raw_result.unwrap()
|
225
363
|
|
226
364
|
# Handle DataFrames specially
|
227
|
-
if effective_model
|
228
|
-
return _create_dataframe_from_data(
|
365
|
+
if effective_model in _DATAFRAME_LIBRARY_MAP and isinstance(effective_model, ModelPreference):
|
366
|
+
return _create_dataframe_from_data(
|
367
|
+
raw_data, effective_model, items_key=items_key, empty_schema=effective_schema
|
368
|
+
)
|
229
369
|
|
230
370
|
# Handle other model types using the same logic as PublicAPI
|
231
371
|
try:
|
@@ -283,7 +423,7 @@ class PrivateAPI:
|
|
283
423
|
"""
|
284
424
|
# Check if DataFrame is requested - not supported for this endpoint
|
285
425
|
effective_model, effective_schema = self._get_effective_model("account", model, schema)
|
286
|
-
if effective_model
|
426
|
+
if effective_model in _DATAFRAME_LIBRARY_MAP:
|
287
427
|
msg = "DataFrame model is not supported due to the shape of data"
|
288
428
|
return Failure(TypeError(msg))
|
289
429
|
|
@@ -774,7 +914,7 @@ class PrivateAPI:
|
|
774
914
|
|
775
915
|
effective_model, effective_schema = self._get_effective_model("deposit", model, schema)
|
776
916
|
|
777
|
-
if effective_model
|
917
|
+
if effective_model in _DATAFRAME_LIBRARY_MAP:
|
778
918
|
msg = "DataFrame model is not supported due to the shape of data"
|
779
919
|
return Failure(TypeError(msg))
|
780
920
|
|
@@ -850,9 +990,11 @@ class PrivateAPI:
|
|
850
990
|
effective_schema: dict | None,
|
851
991
|
) -> Result[Any, BitvavoError]:
|
852
992
|
"""Convert transaction items to the desired model format."""
|
853
|
-
if effective_model
|
854
|
-
# Convert items to DataFrame
|
855
|
-
return _create_dataframe_from_data(
|
993
|
+
if effective_model in _DATAFRAME_LIBRARY_MAP and isinstance(effective_model, ModelPreference):
|
994
|
+
# Convert items to DataFrame using the specific preference
|
995
|
+
return _create_dataframe_from_data(
|
996
|
+
items_data, effective_model, items_key=None, empty_schema=effective_schema
|
997
|
+
)
|
856
998
|
|
857
999
|
if effective_model is Any or effective_model is None:
|
858
1000
|
# Raw data - return items list directly
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
6
6
|
|
7
|
-
import polars as pl
|
8
7
|
from returns.result import Failure, Success
|
9
8
|
|
10
9
|
from bitvavo_client.adapters.returns_adapter import BitvavoError
|
@@ -25,6 +24,17 @@ MAX_TIMESTAMP_VALUE = 8640000000000000 # Maximum allowed timestamp value
|
|
25
24
|
MAX_BOOK_DEPTH = 1000 # Maximum depth for order book
|
26
25
|
MAX_BOOK_REPORT_DEPTH = 1000 # Maximum depth for order book report
|
27
26
|
|
27
|
+
# DataFrames preference to library mapping
|
28
|
+
_DATAFRAME_LIBRARY_MAP = {
|
29
|
+
ModelPreference.POLARS: ("polars", "pl.DataFrame"),
|
30
|
+
ModelPreference.PANDAS: ("pandas", "pd.DataFrame"),
|
31
|
+
ModelPreference.PYARROW: ("pyarrow", "pa.Table"),
|
32
|
+
ModelPreference.DASK: ("dask", "dd.DataFrame"),
|
33
|
+
ModelPreference.MODIN: ("modin", "mpd.DataFrame"),
|
34
|
+
ModelPreference.CUDF: ("cudf", "cudf.DataFrame"),
|
35
|
+
ModelPreference.IBIS: ("ibis", "ibis.Table"),
|
36
|
+
}
|
37
|
+
|
28
38
|
if TYPE_CHECKING: # pragma: no cover
|
29
39
|
from collections.abc import Mapping
|
30
40
|
|
@@ -55,7 +65,21 @@ class PublicAPI:
|
|
55
65
|
default_schema: Default schema for DataFrame conversion
|
56
66
|
"""
|
57
67
|
self.http: HTTPClient = http_client
|
58
|
-
|
68
|
+
|
69
|
+
# Handle preferred_model parameter - try to convert strings to ModelPreference,
|
70
|
+
# but allow arbitrary strings to pass through for custom handling
|
71
|
+
if preferred_model is None:
|
72
|
+
self.preferred_model = None
|
73
|
+
elif isinstance(preferred_model, ModelPreference):
|
74
|
+
self.preferred_model = preferred_model
|
75
|
+
elif isinstance(preferred_model, str):
|
76
|
+
try:
|
77
|
+
self.preferred_model = ModelPreference(preferred_model)
|
78
|
+
except ValueError:
|
79
|
+
# If string doesn't match a valid ModelPreference, store as-is
|
80
|
+
self.preferred_model = preferred_model
|
81
|
+
else:
|
82
|
+
self.preferred_model = preferred_model
|
59
83
|
|
60
84
|
# If using DATAFRAME preference without a default schema, we could provide sensible defaults
|
61
85
|
# But keep it explicit for now - users can import and use schemas as needed
|
@@ -89,10 +113,12 @@ class PublicAPI:
|
|
89
113
|
if self.preferred_model == ModelPreference.RAW:
|
90
114
|
return Any, schema
|
91
115
|
|
92
|
-
|
116
|
+
# Handle all DataFrame preferences
|
117
|
+
if self.preferred_model in _DATAFRAME_LIBRARY_MAP:
|
93
118
|
# Use the provided schema, fallback to instance default, then to endpoint-specific default
|
94
119
|
effective_schema = schema or self.default_schema or DEFAULT_SCHEMAS.get(endpoint_type)
|
95
|
-
|
120
|
+
# Return the preference itself, not a specific DataFrame class
|
121
|
+
return self.preferred_model, effective_schema
|
96
122
|
|
97
123
|
if self.preferred_model == ModelPreference.PYDANTIC:
|
98
124
|
# Map endpoint types to appropriate Pydantic models
|
@@ -140,24 +166,23 @@ class PublicAPI:
|
|
140
166
|
effective_model, effective_schema = self._get_effective_model(endpoint_type, model, schema)
|
141
167
|
|
142
168
|
# If no conversion needed (raw data requested), return as-is
|
143
|
-
if effective_model is Any or effective_model is None:
|
169
|
+
if effective_model is Any or effective_model is None or effective_model == ModelPreference.RAW:
|
144
170
|
return raw_result
|
145
171
|
|
146
172
|
# Extract the raw data
|
147
173
|
raw_data = raw_result.unwrap()
|
148
174
|
|
149
|
-
# Perform conversion
|
175
|
+
# Perform conversion
|
150
176
|
try:
|
151
|
-
# Handle
|
152
|
-
if
|
177
|
+
# Handle DataFrame preferences specially
|
178
|
+
if isinstance(effective_model, ModelPreference) and effective_model in _DATAFRAME_LIBRARY_MAP:
|
179
|
+
parsed = self._create_dataframe(raw_data, effective_model, effective_schema)
|
180
|
+
elif hasattr(effective_model, "model_validate"):
|
153
181
|
# Pydantic model
|
154
182
|
parsed = effective_model.model_validate(raw_data) # type: ignore[misc]
|
155
|
-
|
183
|
+
else:
|
156
184
|
# Simple constructor call - this handles dict and other simple types
|
157
185
|
parsed = effective_model(raw_data) # type: ignore[misc]
|
158
|
-
else:
|
159
|
-
# DataFrame with schema - use type ignoring for now to get working
|
160
|
-
parsed = effective_model(raw_data, schema=effective_schema, strict=False) # type: ignore[misc]
|
161
186
|
|
162
187
|
return Success(parsed)
|
163
188
|
except (ValueError, TypeError, AttributeError) as exc:
|
@@ -171,6 +196,71 @@ class PublicAPI:
|
|
171
196
|
)
|
172
197
|
return Failure(error)
|
173
198
|
|
199
|
+
def _create_dataframe(
|
200
|
+
self,
|
201
|
+
data: Any,
|
202
|
+
preference: ModelPreference,
|
203
|
+
schema: Mapping[str, object] | None,
|
204
|
+
) -> Any:
|
205
|
+
"""Create a DataFrame from raw data using the specified preference.
|
206
|
+
|
207
|
+
Args:
|
208
|
+
data: Raw data to convert
|
209
|
+
preference: DataFrame preference (POLARS, PANDAS, etc.)
|
210
|
+
schema: Schema for DataFrame conversion
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
DataFrame instance
|
214
|
+
|
215
|
+
Raises:
|
216
|
+
ImportError: If the required DataFrame library is not available
|
217
|
+
ValueError: If DataFrame creation fails
|
218
|
+
"""
|
219
|
+
if preference == ModelPreference.POLARS:
|
220
|
+
import polars as pl
|
221
|
+
|
222
|
+
return self._create_polars_dataframe(data, schema, pl)
|
223
|
+
|
224
|
+
if preference == ModelPreference.PANDAS:
|
225
|
+
import pandas as pd
|
226
|
+
|
227
|
+
return pd.DataFrame(data)
|
228
|
+
|
229
|
+
if preference == ModelPreference.PYARROW:
|
230
|
+
import pyarrow as pa
|
231
|
+
|
232
|
+
return pa.Table.from_pylist(data if isinstance(data, list) else [data])
|
233
|
+
|
234
|
+
# For other DataFrame types, try basic conversion
|
235
|
+
msg = f"DataFrame preference {preference} not yet fully implemented"
|
236
|
+
raise NotImplementedError(msg)
|
237
|
+
|
238
|
+
def _create_polars_dataframe(
|
239
|
+
self,
|
240
|
+
data: Any,
|
241
|
+
schema: Mapping[str, object] | None,
|
242
|
+
pl: Any, # polars module
|
243
|
+
) -> Any:
|
244
|
+
"""Create a Polars DataFrame with proper schema handling.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
data: Raw data to convert
|
248
|
+
schema: Schema for DataFrame conversion
|
249
|
+
pl: Polars module
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
Polars DataFrame
|
253
|
+
"""
|
254
|
+
if schema is None:
|
255
|
+
return pl.DataFrame(data)
|
256
|
+
|
257
|
+
# For Polars, we need to handle the schema with strict=False for compatibility
|
258
|
+
try:
|
259
|
+
return pl.DataFrame(data, schema=schema, strict=False)
|
260
|
+
except Exception:
|
261
|
+
# Fallback to basic DataFrame creation if schema fails
|
262
|
+
return pl.DataFrame(data)
|
263
|
+
|
174
264
|
def time(
|
175
265
|
self,
|
176
266
|
*,
|
File without changes
|