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.
@@ -31,7 +31,7 @@ def is_library_available(library_name: str) -> bool:
31
31
  "cudf": "cudf",
32
32
  "modin": "modin.pandas",
33
33
  "pyarrow": "pyarrow",
34
- "dask": "dask.dataframe",
34
+ "dask": "dask",
35
35
  "duckdb": "duckdb",
36
36
  "ibis": "ibis",
37
37
  "pyspark": "pyspark.sql",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bitvavo-api-upgraded
3
- Version: 4.1.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.DATAFRAME)
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
- df_data = client.public.markets(model=pl.DataFrame)
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=UvcDM0HeE-thUvsm9EjCmddmGBzZ9Puu40UVa0fR_p8,5821
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=ThSUphaGVkcpQplJHV3IVe7l1OyUUDJgEWQvw-ZlS8w,14056
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=JUJRoO57gOKPUiuitHM9HqI6vTawILDwWPQEGDgj59I,844
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=r_6yZt9hbzjqiAH7KFOp9SxZvdy75LAQU6IbB08cyiE,42614
27
- bitvavo_client/endpoints/public.py,sha256=ejfTOJgkKNbPKZqnw6Jcww7rXInFTEIhqvTGu_NbyX8,26433
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.1.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
37
- bitvavo_api_upgraded-4.1.1.dist-info/METADATA,sha256=OY1GLerppyA0Njjs4mz3E418n0MqoKr30w-mlYNIS8c,35857
38
- bitvavo_api_upgraded-4.1.1.dist-info/RECORD,,
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
- import polars as pl # noqa: PLC0415
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 (requires schema)
27
- DATAFRAME = "dataframe"
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[pl.DataFrame, BitvavoError]:
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 with flexible schema first
74
- df = pl.DataFrame(df_data, strict=False)
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 with provided schema or minimal default
87
- if empty_schema is None:
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, pl.exceptions.PolarsError) as exc:
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
- self.preferred_model = ModelPreference(preferred_model) if preferred_model else None
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
- if self.preferred_model == ModelPreference.DATAFRAME:
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
- return pl.DataFrame, effective_schema
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 is pl.DataFrame:
228
- return _create_dataframe_from_data(raw_data, items_key=items_key, empty_schema=effective_schema)
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 is pl.DataFrame:
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 is pl.DataFrame:
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 is pl.DataFrame:
854
- # Convert items to DataFrame
855
- return _create_dataframe_from_data(items_data, items_key=None, empty_schema=effective_schema)
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
- self.preferred_model = ModelPreference(preferred_model) if preferred_model else None
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
- if self.preferred_model == ModelPreference.DATAFRAME:
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
- return pl.DataFrame, effective_schema
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 using the same logic as the returns adapter
175
+ # Perform conversion
150
176
  try:
151
- # Handle different model types
152
- if hasattr(effective_model, "model_validate"):
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
- elif effective_schema is None:
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
  *,