apache-hamilton 1.90.0.dev0__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.
- apache_hamilton-1.90.0.dev0.dist-info/METADATA +407 -0
- apache_hamilton-1.90.0.dev0.dist-info/RECORD +151 -0
- apache_hamilton-1.90.0.dev0.dist-info/WHEEL +4 -0
- apache_hamilton-1.90.0.dev0.dist-info/entry_points.txt +9 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/DISCLAIMER +10 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/LICENSE +228 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/NOTICE +5 -0
- hamilton/__init__.py +24 -0
- hamilton/ad_hoc_utils.py +132 -0
- hamilton/async_driver.py +465 -0
- hamilton/base.py +466 -0
- hamilton/caching/__init__.py +16 -0
- hamilton/caching/adapter.py +1475 -0
- hamilton/caching/cache_key.py +70 -0
- hamilton/caching/fingerprinting.py +287 -0
- hamilton/caching/stores/__init__.py +16 -0
- hamilton/caching/stores/base.py +242 -0
- hamilton/caching/stores/file.py +140 -0
- hamilton/caching/stores/memory.py +297 -0
- hamilton/caching/stores/sqlite.py +282 -0
- hamilton/caching/stores/utils.py +40 -0
- hamilton/cli/__init__.py +16 -0
- hamilton/cli/__main__.py +328 -0
- hamilton/cli/commands.py +126 -0
- hamilton/cli/logic.py +338 -0
- hamilton/common/__init__.py +76 -0
- hamilton/contrib/__init__.py +41 -0
- hamilton/data_quality/__init__.py +16 -0
- hamilton/data_quality/base.py +198 -0
- hamilton/data_quality/default_validators.py +560 -0
- hamilton/data_quality/pandera_validators.py +121 -0
- hamilton/dataflows/__init__.py +726 -0
- hamilton/dataflows/template/README.md +25 -0
- hamilton/dataflows/template/__init__.py +54 -0
- hamilton/dataflows/template/author.md +28 -0
- hamilton/dataflows/template/requirements.txt +0 -0
- hamilton/dataflows/template/tags.json +7 -0
- hamilton/dataflows/template/valid_configs.jsonl +1 -0
- hamilton/dev_utils/__init__.py +16 -0
- hamilton/dev_utils/deprecation.py +204 -0
- hamilton/driver.py +2112 -0
- hamilton/execution/__init__.py +16 -0
- hamilton/execution/debugging_utils.py +56 -0
- hamilton/execution/executors.py +502 -0
- hamilton/execution/graph_functions.py +421 -0
- hamilton/execution/grouping.py +430 -0
- hamilton/execution/state.py +539 -0
- hamilton/experimental/__init__.py +27 -0
- hamilton/experimental/databackend.py +61 -0
- hamilton/experimental/decorators/__init__.py +16 -0
- hamilton/experimental/decorators/parameterize_frame.py +233 -0
- hamilton/experimental/h_async.py +29 -0
- hamilton/experimental/h_cache.py +413 -0
- hamilton/experimental/h_dask.py +28 -0
- hamilton/experimental/h_databackends.py +174 -0
- hamilton/experimental/h_ray.py +28 -0
- hamilton/experimental/h_spark.py +32 -0
- hamilton/function_modifiers/README +40 -0
- hamilton/function_modifiers/__init__.py +121 -0
- hamilton/function_modifiers/adapters.py +900 -0
- hamilton/function_modifiers/base.py +859 -0
- hamilton/function_modifiers/configuration.py +310 -0
- hamilton/function_modifiers/delayed.py +202 -0
- hamilton/function_modifiers/dependencies.py +246 -0
- hamilton/function_modifiers/expanders.py +1230 -0
- hamilton/function_modifiers/macros.py +1634 -0
- hamilton/function_modifiers/metadata.py +434 -0
- hamilton/function_modifiers/recursive.py +908 -0
- hamilton/function_modifiers/validation.py +289 -0
- hamilton/function_modifiers_base.py +31 -0
- hamilton/graph.py +1153 -0
- hamilton/graph_types.py +264 -0
- hamilton/graph_utils.py +41 -0
- hamilton/htypes.py +450 -0
- hamilton/io/__init__.py +32 -0
- hamilton/io/data_adapters.py +216 -0
- hamilton/io/default_data_loaders.py +224 -0
- hamilton/io/materialization.py +500 -0
- hamilton/io/utils.py +158 -0
- hamilton/lifecycle/__init__.py +67 -0
- hamilton/lifecycle/api.py +833 -0
- hamilton/lifecycle/base.py +1130 -0
- hamilton/lifecycle/default.py +802 -0
- hamilton/log_setup.py +47 -0
- hamilton/models.py +92 -0
- hamilton/node.py +449 -0
- hamilton/plugins/README.md +48 -0
- hamilton/plugins/__init__.py +16 -0
- hamilton/plugins/dask_extensions.py +47 -0
- hamilton/plugins/dlt_extensions.py +161 -0
- hamilton/plugins/geopandas_extensions.py +49 -0
- hamilton/plugins/h_dask.py +331 -0
- hamilton/plugins/h_ddog.py +522 -0
- hamilton/plugins/h_diskcache.py +163 -0
- hamilton/plugins/h_experiments/__init__.py +22 -0
- hamilton/plugins/h_experiments/__main__.py +62 -0
- hamilton/plugins/h_experiments/cache.py +39 -0
- hamilton/plugins/h_experiments/data_model.py +68 -0
- hamilton/plugins/h_experiments/hook.py +219 -0
- hamilton/plugins/h_experiments/server.py +375 -0
- hamilton/plugins/h_kedro.py +152 -0
- hamilton/plugins/h_logging.py +454 -0
- hamilton/plugins/h_mcp/__init__.py +28 -0
- hamilton/plugins/h_mcp/__main__.py +33 -0
- hamilton/plugins/h_mcp/_helpers.py +129 -0
- hamilton/plugins/h_mcp/_templates.py +417 -0
- hamilton/plugins/h_mcp/server.py +328 -0
- hamilton/plugins/h_mlflow.py +335 -0
- hamilton/plugins/h_narwhals.py +134 -0
- hamilton/plugins/h_openlineage.py +400 -0
- hamilton/plugins/h_opentelemetry.py +167 -0
- hamilton/plugins/h_pandas.py +257 -0
- hamilton/plugins/h_pandera.py +117 -0
- hamilton/plugins/h_polars.py +304 -0
- hamilton/plugins/h_polars_lazyframe.py +282 -0
- hamilton/plugins/h_pyarrow.py +56 -0
- hamilton/plugins/h_pydantic.py +127 -0
- hamilton/plugins/h_ray.py +242 -0
- hamilton/plugins/h_rich.py +142 -0
- hamilton/plugins/h_schema.py +493 -0
- hamilton/plugins/h_slack.py +100 -0
- hamilton/plugins/h_spark.py +1380 -0
- hamilton/plugins/h_threadpool.py +125 -0
- hamilton/plugins/h_tqdm.py +122 -0
- hamilton/plugins/h_vaex.py +129 -0
- hamilton/plugins/huggingface_extensions.py +236 -0
- hamilton/plugins/ibis_extensions.py +93 -0
- hamilton/plugins/jupyter_magic.py +622 -0
- hamilton/plugins/kedro_extensions.py +117 -0
- hamilton/plugins/lightgbm_extensions.py +99 -0
- hamilton/plugins/matplotlib_extensions.py +108 -0
- hamilton/plugins/mlflow_extensions.py +216 -0
- hamilton/plugins/numpy_extensions.py +105 -0
- hamilton/plugins/pandas_extensions.py +1763 -0
- hamilton/plugins/plotly_extensions.py +150 -0
- hamilton/plugins/polars_extensions.py +71 -0
- hamilton/plugins/polars_implementations.py +25 -0
- hamilton/plugins/polars_lazyframe_extensions.py +302 -0
- hamilton/plugins/polars_post_1_0_0_extensions.py +877 -0
- hamilton/plugins/polars_pre_1_0_0_extension.py +836 -0
- hamilton/plugins/pydantic_extensions.py +98 -0
- hamilton/plugins/pyspark_pandas_extensions.py +47 -0
- hamilton/plugins/sklearn_plot_extensions.py +129 -0
- hamilton/plugins/spark_extensions.py +105 -0
- hamilton/plugins/vaex_extensions.py +51 -0
- hamilton/plugins/xgboost_extensions.py +91 -0
- hamilton/plugins/yaml_extensions.py +89 -0
- hamilton/registry.py +254 -0
- hamilton/settings.py +18 -0
- hamilton/telemetry.py +50 -0
- hamilton/version.py +18 -0
|
@@ -0,0 +1,1763 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
import abc
|
|
19
|
+
import csv
|
|
20
|
+
import dataclasses
|
|
21
|
+
from collections.abc import Callable, Collection, Hashable, Iterator
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from io import BufferedReader, BytesIO, StringIO
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, TypeAlias
|
|
26
|
+
|
|
27
|
+
from packaging.version import Version
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
import pandas as pd
|
|
31
|
+
except ImportError as e:
|
|
32
|
+
raise NotImplementedError("Pandas is not installed.") from e
|
|
33
|
+
|
|
34
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
35
|
+
from typing import Literal
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import fsspec
|
|
39
|
+
import pyarrow.fs
|
|
40
|
+
|
|
41
|
+
FILESYSTEM_TYPE = pyarrow.fs.FileSystem | fsspec.spec.AbstractFileSystem | None
|
|
42
|
+
except ImportError:
|
|
43
|
+
FILESYSTEM_TYPE = type | None
|
|
44
|
+
|
|
45
|
+
from sqlite3 import Connection
|
|
46
|
+
|
|
47
|
+
from pandas._typing import NpDtype
|
|
48
|
+
from pandas.core.dtypes.dtypes import ExtensionDtype
|
|
49
|
+
|
|
50
|
+
from hamilton import registry
|
|
51
|
+
from hamilton.io import utils
|
|
52
|
+
from hamilton.io.data_adapters import DataLoader, DataSaver
|
|
53
|
+
|
|
54
|
+
DATAFRAME_TYPE = pd.DataFrame
|
|
55
|
+
COLUMN_TYPE = pd.Series
|
|
56
|
+
|
|
57
|
+
JSONSerializable: TypeAlias = str | float | bool | list | dict | None
|
|
58
|
+
IndexLabel: TypeAlias = Hashable | Iterator[Hashable] | None
|
|
59
|
+
Dtype: TypeAlias = ExtensionDtype | NpDtype
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@registry.get_column.register(pd.DataFrame)
|
|
63
|
+
def get_column_pandas(df: pd.DataFrame, column_name: str) -> pd.Series:
|
|
64
|
+
return df[column_name]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@registry.fill_with_scalar.register(pd.DataFrame)
|
|
68
|
+
def fill_with_scalar_pandas(df: pd.DataFrame, column_name: str, value: Any) -> pd.DataFrame:
|
|
69
|
+
df[column_name] = value
|
|
70
|
+
return df
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def register_types():
|
|
74
|
+
"""Function to register the types for this extension."""
|
|
75
|
+
registry.register_types("pandas", DATAFRAME_TYPE, COLUMN_TYPE)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
register_types()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DataFrameDataLoader(DataLoader, DataSaver, abc.ABC):
|
|
82
|
+
"""Base class for data loaders that saves/loads pandas dataframes.
|
|
83
|
+
Note that these are currently grouped together, but this could change!
|
|
84
|
+
We can change this as these are not part of the publicly exposed APIs.
|
|
85
|
+
Rather, the fixed component is the keys (E.G. csv, feather, etc...) , which,
|
|
86
|
+
when combined with types, correspond to a group of specific parameter. As such,
|
|
87
|
+
the backwards-compatible invariance enables us to change the implementation
|
|
88
|
+
(which classes), and so long as the set of parameters/load targets are compatible,
|
|
89
|
+
we are good to go."""
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def applicable_types(cls) -> Collection[type]:
|
|
93
|
+
return [DATAFRAME_TYPE]
|
|
94
|
+
|
|
95
|
+
@abc.abstractmethod
|
|
96
|
+
def load_data(self, type_: type[DATAFRAME_TYPE]) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
@abc.abstractmethod
|
|
100
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclasses.dataclass
|
|
105
|
+
class PandasCSVReader(DataLoader):
|
|
106
|
+
"""
|
|
107
|
+
Class that handles saving CSV files with pandas.
|
|
108
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# the filepath_or_buffer param will be changed to path for backwards compatibility
|
|
112
|
+
path: str | Path | BytesIO | BufferedReader
|
|
113
|
+
# kwargs
|
|
114
|
+
sep: str | None = ","
|
|
115
|
+
delimiter: str | None = None
|
|
116
|
+
header: Sequence | int | Literal["infer"] | None = "infer"
|
|
117
|
+
names: Sequence | None = None
|
|
118
|
+
index_col: Hashable | Sequence | Literal[False] | None = None
|
|
119
|
+
usecols: list[Hashable] | Callable | tuple | None = None
|
|
120
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
121
|
+
engine: Literal["c", "python", "pyarrow", "python-fwf"] | None = None
|
|
122
|
+
converters: Mapping | None = None
|
|
123
|
+
true_values: list | None = None
|
|
124
|
+
false_values: list | None = None
|
|
125
|
+
skipinitialspace: bool | None = False
|
|
126
|
+
skiprows: list[int] | int | Callable[[Hashable], bool] | None = None
|
|
127
|
+
skipfooter: int = 0
|
|
128
|
+
nrows: int | None = None
|
|
129
|
+
na_values: Hashable | Iterable | Mapping | None = None
|
|
130
|
+
keep_default_na: bool = True
|
|
131
|
+
na_filter: bool = True
|
|
132
|
+
verbose: bool | None = None
|
|
133
|
+
skip_blank_lines: bool = True
|
|
134
|
+
parse_dates: bool | Sequence | None | None = False
|
|
135
|
+
keep_date_col: bool | None = None
|
|
136
|
+
date_format: str | None = None
|
|
137
|
+
dayfirst: bool = False
|
|
138
|
+
cache_dates: bool = True
|
|
139
|
+
iterator: bool = False
|
|
140
|
+
chunksize: int | None = None
|
|
141
|
+
compression: (
|
|
142
|
+
Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] | dict[str, Any] | None
|
|
143
|
+
) = "infer"
|
|
144
|
+
thousands: str | None = None
|
|
145
|
+
decimal: str = "."
|
|
146
|
+
lineterminator: str | None = None
|
|
147
|
+
quotechar: str | None = None
|
|
148
|
+
quoting: int = 0
|
|
149
|
+
doublequote: bool = True
|
|
150
|
+
escapechar: str | None = None
|
|
151
|
+
comment: str | None = None
|
|
152
|
+
encoding: str = "utf-8"
|
|
153
|
+
encoding_errors: (
|
|
154
|
+
Literal["strict", "ignore", "replace", "backslashreplace", "surrogateescape"] | str
|
|
155
|
+
) = "strict"
|
|
156
|
+
dialect: str | csv.Dialect | None = None
|
|
157
|
+
on_bad_lines: Literal["error", "warn", "skip"] | Callable = "error"
|
|
158
|
+
delim_whitespace: bool | None = None
|
|
159
|
+
low_memory: bool = True
|
|
160
|
+
memory_map: bool = False
|
|
161
|
+
float_precision: Literal["high", "legacy", "round_trip"] | None = None
|
|
162
|
+
storage_options: dict[str, Any] | None = None
|
|
163
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def applicable_types(cls) -> Collection[type]:
|
|
167
|
+
return [DATAFRAME_TYPE]
|
|
168
|
+
|
|
169
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
170
|
+
kwargs = {}
|
|
171
|
+
if self.sep is not None:
|
|
172
|
+
kwargs["sep"] = self.sep
|
|
173
|
+
if self.delimiter is not None:
|
|
174
|
+
kwargs["delimiter"] = self.delimiter
|
|
175
|
+
if self.header is not None:
|
|
176
|
+
kwargs["header"] = self.header
|
|
177
|
+
if self.names is not None:
|
|
178
|
+
kwargs["names"] = self.names
|
|
179
|
+
if self.index_col is not None:
|
|
180
|
+
kwargs["index_col"] = self.index_col
|
|
181
|
+
if self.usecols is not None:
|
|
182
|
+
kwargs["usecols"] = self.usecols
|
|
183
|
+
if self.dtype is not None:
|
|
184
|
+
kwargs["dtype"] = self.dtype
|
|
185
|
+
if self.engine is not None:
|
|
186
|
+
kwargs["engine"] = self.engine
|
|
187
|
+
if self.converters is not None:
|
|
188
|
+
kwargs["converters"] = self.converters
|
|
189
|
+
if self.true_values is not None:
|
|
190
|
+
kwargs["true_values"] = self.true_values
|
|
191
|
+
if self.false_values is not None:
|
|
192
|
+
kwargs["false_values"] = self.false_values
|
|
193
|
+
if self.skipinitialspace is not None:
|
|
194
|
+
kwargs["skipinitialspace"] = self.skipinitialspace
|
|
195
|
+
if self.skiprows is not None:
|
|
196
|
+
kwargs["skiprows"] = self.skiprows
|
|
197
|
+
if self.nrows is not None:
|
|
198
|
+
kwargs["nrows"] = self.nrows
|
|
199
|
+
if self.na_values is not None:
|
|
200
|
+
kwargs["na_values"] = self.na_values
|
|
201
|
+
if self.keep_default_na is not None:
|
|
202
|
+
kwargs["keep_default_na"] = self.keep_default_na
|
|
203
|
+
if self.na_filter is not None:
|
|
204
|
+
kwargs["na_filter"] = self.na_filter
|
|
205
|
+
if Version(pd.__version__) < Version("2.2") and self.verbose is not None:
|
|
206
|
+
kwargs["verbose"] = self.verbose
|
|
207
|
+
if self.skip_blank_lines is not None:
|
|
208
|
+
kwargs["skip_blank_lines"] = self.skip_blank_lines
|
|
209
|
+
if self.parse_dates is not None:
|
|
210
|
+
kwargs["parse_dates"] = self.parse_dates
|
|
211
|
+
if Version(pd.__version__) < Version("2.2") and self.keep_date_col is not None:
|
|
212
|
+
kwargs["keep_date_col"] = self.keep_date_col
|
|
213
|
+
if self.date_format is not None:
|
|
214
|
+
kwargs["date_format"] = self.date_format
|
|
215
|
+
if self.dayfirst is not None:
|
|
216
|
+
kwargs["dayfirst"] = self.dayfirst
|
|
217
|
+
if self.cache_dates is not None:
|
|
218
|
+
kwargs["cache_dates"] = self.cache_dates
|
|
219
|
+
if self.iterator is not None:
|
|
220
|
+
kwargs["iterator"] = self.iterator
|
|
221
|
+
if self.chunksize is not None:
|
|
222
|
+
kwargs["chunksize"] = self.chunksize
|
|
223
|
+
if self.compression is not None:
|
|
224
|
+
kwargs["compression"] = self.compression
|
|
225
|
+
if self.thousands is not None:
|
|
226
|
+
kwargs["thousands"] = self.thousands
|
|
227
|
+
if self.lineterminator is not None:
|
|
228
|
+
kwargs["lineterminator"] = self.lineterminator
|
|
229
|
+
if self.quotechar is not None:
|
|
230
|
+
kwargs["quotechar"] = self.quotechar
|
|
231
|
+
if self.quoting is not None:
|
|
232
|
+
kwargs["quoting"] = self.quoting
|
|
233
|
+
if self.doublequote is not None:
|
|
234
|
+
kwargs["doublequote"] = self.doublequote
|
|
235
|
+
if self.escapechar is not None:
|
|
236
|
+
kwargs["escapechar"] = self.escapechar
|
|
237
|
+
if self.comment is not None:
|
|
238
|
+
kwargs["comment"] = self.comment
|
|
239
|
+
if self.encoding is not None:
|
|
240
|
+
kwargs["encoding"] = self.encoding
|
|
241
|
+
if self.encoding_errors is not None:
|
|
242
|
+
kwargs["encoding_errors"] = self.encoding_errors
|
|
243
|
+
if self.dialect is not None:
|
|
244
|
+
kwargs["dialect"] = self.dialect
|
|
245
|
+
if self.on_bad_lines is not None:
|
|
246
|
+
kwargs["on_bad_lines"] = self.on_bad_lines
|
|
247
|
+
if Version(pd.__version__) < Version("2.2") and self.delim_whitespace is not None:
|
|
248
|
+
kwargs["delim_whitespace"] = self.delim_whitespace
|
|
249
|
+
if self.low_memory is not None:
|
|
250
|
+
kwargs["low_memory"] = self.low_memory
|
|
251
|
+
if self.memory_map is not None:
|
|
252
|
+
kwargs["memory_map"] = self.memory_map
|
|
253
|
+
if self.float_precision is not None:
|
|
254
|
+
kwargs["float_precision"] = self.float_precision
|
|
255
|
+
if self.storage_options is not None:
|
|
256
|
+
kwargs["storage_options"] = self.storage_options
|
|
257
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
258
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
259
|
+
|
|
260
|
+
return kwargs
|
|
261
|
+
|
|
262
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
263
|
+
df = pd.read_csv(self.path, **self._get_loading_kwargs())
|
|
264
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
265
|
+
return df, metadata
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def name(cls) -> str:
|
|
269
|
+
return "csv"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclasses.dataclass
|
|
273
|
+
class PandasCSVWriter(DataSaver):
|
|
274
|
+
"""Class that handles saving CSV files with pandas.
|
|
275
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
path: str | Path | BytesIO | BufferedReader
|
|
279
|
+
# kwargs
|
|
280
|
+
sep: str | None = ","
|
|
281
|
+
na_rep: str = ""
|
|
282
|
+
float_format: str | Callable | None = None
|
|
283
|
+
columns: Sequence | None = None
|
|
284
|
+
header: bool | list[str] | None = True
|
|
285
|
+
index: bool | None = False
|
|
286
|
+
index_label: IndexLabel | None = None
|
|
287
|
+
mode: str = "w"
|
|
288
|
+
encoding: str | None = None
|
|
289
|
+
compression: (
|
|
290
|
+
Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] | dict[str, Any] | None
|
|
291
|
+
) = "infer"
|
|
292
|
+
quoting: int | None = None
|
|
293
|
+
quotechar: str | None = '"'
|
|
294
|
+
lineterminator: str | None = None
|
|
295
|
+
chunksize: int | None = None
|
|
296
|
+
date_format: str | None = None
|
|
297
|
+
doublequote: bool = True
|
|
298
|
+
escapechar: str | None = None
|
|
299
|
+
decimal: str = "."
|
|
300
|
+
errors: str = "strict"
|
|
301
|
+
storage_options: dict[str, Any] | None = None
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def applicable_types(cls) -> Collection[type]:
|
|
305
|
+
return [DATAFRAME_TYPE]
|
|
306
|
+
|
|
307
|
+
def _get_saving_kwargs(self) -> dict[str, Any]:
|
|
308
|
+
# Puts kwargs in a dict
|
|
309
|
+
kwargs = {}
|
|
310
|
+
if self.sep is not None:
|
|
311
|
+
kwargs["sep"] = self.sep
|
|
312
|
+
if self.na_rep is not None:
|
|
313
|
+
kwargs["na_rep"] = self.na_rep
|
|
314
|
+
if self.float_format is not None:
|
|
315
|
+
kwargs["float_format"] = self.float_format
|
|
316
|
+
if self.columns is not None:
|
|
317
|
+
kwargs["columns"] = self.columns
|
|
318
|
+
if self.header is not None:
|
|
319
|
+
kwargs["header"] = self.header
|
|
320
|
+
if self.index is not None:
|
|
321
|
+
kwargs["index"] = self.index
|
|
322
|
+
if self.index_label is not None:
|
|
323
|
+
kwargs["index_label"] = self.index_label
|
|
324
|
+
if self.mode is not None:
|
|
325
|
+
kwargs["mode"] = self.mode
|
|
326
|
+
if self.encoding is not None:
|
|
327
|
+
kwargs["encoding"] = self.encoding
|
|
328
|
+
if self.compression is not None:
|
|
329
|
+
kwargs["compression"] = self.compression
|
|
330
|
+
if self.quoting is not None:
|
|
331
|
+
kwargs["quoting"] = self.quoting
|
|
332
|
+
if self.quotechar is not None:
|
|
333
|
+
kwargs["quotechar"] = self.quotechar
|
|
334
|
+
if self.lineterminator is not None:
|
|
335
|
+
kwargs["lineterminator"] = self.lineterminator
|
|
336
|
+
if self.chunksize is not None:
|
|
337
|
+
kwargs["chunksize"] = self.chunksize
|
|
338
|
+
if self.date_format is not None:
|
|
339
|
+
kwargs["date_format"] = self.date_format
|
|
340
|
+
if self.doublequote is not None:
|
|
341
|
+
kwargs["doublequote"] = self.doublequote
|
|
342
|
+
if self.escapechar is not None:
|
|
343
|
+
kwargs["escapechar"] = self.escapechar
|
|
344
|
+
if self.decimal is not None:
|
|
345
|
+
kwargs["decimal"] = self.decimal
|
|
346
|
+
if self.errors is not None:
|
|
347
|
+
kwargs["errors"] = self.errors
|
|
348
|
+
if self.storage_options is not None:
|
|
349
|
+
kwargs["storage_options"] = self.storage_options
|
|
350
|
+
|
|
351
|
+
return kwargs
|
|
352
|
+
|
|
353
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
354
|
+
data.to_csv(self.path, **self._get_saving_kwargs())
|
|
355
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def name(cls) -> str:
|
|
359
|
+
return "csv"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclasses.dataclass
|
|
363
|
+
class PandasParquetReader(DataLoader):
|
|
364
|
+
"""Class that handles saving parquet files with pandas.
|
|
365
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_parquet.html#pandas.read_parquet
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
path: str | Path | BytesIO | BufferedReader
|
|
369
|
+
# kwargs
|
|
370
|
+
engine: Literal["auto", "pyarrow", "fastparquet"] = "auto"
|
|
371
|
+
columns: list[str] | None = None
|
|
372
|
+
storage_options: dict[str, Any] | None = None
|
|
373
|
+
use_nullable_dtypes: bool = False
|
|
374
|
+
dtype_backend: Literal["numpy_nullable", "pyarrow"] = "numpy_nullable"
|
|
375
|
+
filesystem: str | None = None
|
|
376
|
+
filters: list[tuple] | list[list[tuple]] | None = None
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def applicable_types(cls) -> Collection[type]:
|
|
380
|
+
return [DATAFRAME_TYPE]
|
|
381
|
+
|
|
382
|
+
def _get_loading_kwargs(self):
|
|
383
|
+
kwargs = {}
|
|
384
|
+
if self.engine is not None:
|
|
385
|
+
kwargs["engine"] = self.engine
|
|
386
|
+
if self.columns is not None:
|
|
387
|
+
kwargs["columns"] = self.columns
|
|
388
|
+
if self.storage_options is not None:
|
|
389
|
+
kwargs["storage_options"] = self.storage_options
|
|
390
|
+
if Version(pd.__version__) < Version("2.0") and self.use_nullable_dtypes is not None:
|
|
391
|
+
kwargs["use_nullable_dtypes"] = self.use_nullable_dtypes
|
|
392
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
393
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
394
|
+
if self.filesystem is not None:
|
|
395
|
+
kwargs["filesystem"] = self.filesystem
|
|
396
|
+
if self.filters is not None:
|
|
397
|
+
kwargs["filters"] = self.filters
|
|
398
|
+
|
|
399
|
+
return kwargs
|
|
400
|
+
|
|
401
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
402
|
+
# Loads the data and returns the df and metadata of the pickle
|
|
403
|
+
df = pd.read_parquet(self.path, **self._get_loading_kwargs())
|
|
404
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
405
|
+
return df, metadata
|
|
406
|
+
|
|
407
|
+
@classmethod
|
|
408
|
+
def name(cls) -> str:
|
|
409
|
+
return "parquet"
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@dataclasses.dataclass
|
|
413
|
+
class PandasParquetWriter(DataSaver):
|
|
414
|
+
"""Class that handles saving parquet files with pandas.
|
|
415
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_parquet.html#pandas.DataFrame.to_parquet
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
path: str | Path | BytesIO | BufferedReader
|
|
419
|
+
# kwargs
|
|
420
|
+
engine: Literal["auto", "pyarrow", "fastparquet"] = "auto"
|
|
421
|
+
compression: str | None = "snappy"
|
|
422
|
+
index: bool | None = None
|
|
423
|
+
partition_cols: list[str] | None = None
|
|
424
|
+
storage_options: dict[str, Any] | None = None
|
|
425
|
+
extra_kwargs: dict[str, Any] | None = None
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def applicable_types(cls) -> Collection[type]:
|
|
429
|
+
return [DATAFRAME_TYPE]
|
|
430
|
+
|
|
431
|
+
def _get_saving_kwargs(self) -> dict[str, Any]:
|
|
432
|
+
# Puts kwargs in a dict
|
|
433
|
+
kwargs = {}
|
|
434
|
+
if self.engine is not None:
|
|
435
|
+
kwargs["engine"] = self.engine
|
|
436
|
+
if self.compression is not None:
|
|
437
|
+
kwargs["compression"] = self.compression
|
|
438
|
+
if self.index is not None:
|
|
439
|
+
kwargs["index"] = self.index
|
|
440
|
+
if self.partition_cols is not None:
|
|
441
|
+
kwargs["partition_cols"] = self.partition_cols
|
|
442
|
+
if self.storage_options is not None:
|
|
443
|
+
kwargs["storage_options"] = self.storage_options
|
|
444
|
+
if self.extra_kwargs is not None:
|
|
445
|
+
kwargs.update(self.extra_kwargs)
|
|
446
|
+
return kwargs
|
|
447
|
+
|
|
448
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
449
|
+
data.to_parquet(self.path, **self._get_saving_kwargs())
|
|
450
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
451
|
+
|
|
452
|
+
@classmethod
|
|
453
|
+
def name(cls) -> str:
|
|
454
|
+
return "parquet"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@dataclasses.dataclass
|
|
458
|
+
class PandasPickleReader(DataLoader):
|
|
459
|
+
"""Class for loading/reading pickle files with Pandas.
|
|
460
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_pickle.html#pandas.read_pickle
|
|
461
|
+
"""
|
|
462
|
+
|
|
463
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader = None
|
|
464
|
+
path: str | Path | BytesIO | BufferedReader = (
|
|
465
|
+
None # alias for `filepath_or_buffer` to keep reading/writing args symmetric.
|
|
466
|
+
)
|
|
467
|
+
# kwargs:
|
|
468
|
+
compression: str | dict[str, Any] | None = "infer"
|
|
469
|
+
storage_options: dict[str, Any] | None = None
|
|
470
|
+
|
|
471
|
+
@classmethod
|
|
472
|
+
def applicable_types(cls) -> Collection[type]:
|
|
473
|
+
# Returns type for which data loader is available
|
|
474
|
+
return [DATAFRAME_TYPE]
|
|
475
|
+
|
|
476
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
477
|
+
# Puts kwargs in a dict
|
|
478
|
+
kwargs = {}
|
|
479
|
+
if self.compression is not None:
|
|
480
|
+
kwargs["compression"] = self.compression
|
|
481
|
+
if self.storage_options is not None:
|
|
482
|
+
kwargs["storage_options"] = self.storage_options
|
|
483
|
+
return kwargs
|
|
484
|
+
|
|
485
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
486
|
+
# Loads the data and returns the df and metadata of the pickle
|
|
487
|
+
df = pd.read_pickle(self.filepath_or_buffer, **self._get_loading_kwargs())
|
|
488
|
+
metadata = utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, df)
|
|
489
|
+
return df, metadata
|
|
490
|
+
|
|
491
|
+
@classmethod
|
|
492
|
+
def name(cls) -> str:
|
|
493
|
+
return "pickle"
|
|
494
|
+
|
|
495
|
+
def __post_init__(self):
|
|
496
|
+
"""As we're adding in a path alias for filepath_or_buffer, we need to ensure that
|
|
497
|
+
we have backwards compatibility with the old parameter. That means that:
|
|
498
|
+
1. Either filepath_or_buffer or path must be specified, not both
|
|
499
|
+
2. If path is specified, filepath_or_buffer is set to path
|
|
500
|
+
"""
|
|
501
|
+
if self.filepath_or_buffer is None and self.path is None:
|
|
502
|
+
raise ValueError("Either filepath_or_buffer or path must be specified")
|
|
503
|
+
elif self.filepath_or_buffer is not None and self.path is not None:
|
|
504
|
+
raise ValueError("Only one of filepath_or_buffer or path must be specified")
|
|
505
|
+
elif self.filepath_or_buffer is None:
|
|
506
|
+
self.filepath_or_buffer = self.path
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
pickle_protocol_default = 5
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@dataclasses.dataclass
|
|
513
|
+
class PandasPickleWriter(DataSaver):
|
|
514
|
+
"""Class that handles saving pickle files with pandas.
|
|
515
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_pickle.html#pandas.DataFrame.to_pickle
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
path: str | Path | BytesIO | BufferedReader
|
|
519
|
+
# kwargs:
|
|
520
|
+
compression: str | dict[str, Any] | None = "infer"
|
|
521
|
+
protocol: int = pickle_protocol_default
|
|
522
|
+
storage_options: dict[str, Any] | None = None
|
|
523
|
+
|
|
524
|
+
@classmethod
|
|
525
|
+
def applicable_types(cls) -> Collection[type]:
|
|
526
|
+
return [DATAFRAME_TYPE]
|
|
527
|
+
|
|
528
|
+
def _get_saving_kwargs(self) -> dict[str, Any]:
|
|
529
|
+
# Puts kwargs in a dict
|
|
530
|
+
kwargs = {}
|
|
531
|
+
if self.compression is not None:
|
|
532
|
+
kwargs["compression"] = self.compression
|
|
533
|
+
if self.protocol is not None:
|
|
534
|
+
kwargs["protocol"] = self.protocol
|
|
535
|
+
if self.storage_options is not None:
|
|
536
|
+
kwargs["storage_options"] = self.storage_options
|
|
537
|
+
return kwargs
|
|
538
|
+
|
|
539
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
540
|
+
data.to_pickle(self.path, **self._get_saving_kwargs())
|
|
541
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
542
|
+
|
|
543
|
+
@classmethod
|
|
544
|
+
def name(cls) -> str:
|
|
545
|
+
return "pickle"
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
@dataclasses.dataclass
|
|
549
|
+
class PandasJsonReader(DataLoader):
|
|
550
|
+
"""Class specifically to handle loading JSON files/buffers with Pandas.
|
|
551
|
+
|
|
552
|
+
Disclaimer: We're exposing all the *current* params from the Pandas read_json method.
|
|
553
|
+
Some of these params may get deprecated or new params may be introduced. In the event that
|
|
554
|
+
the params/kwargs below become outdated, please raise an issue or submit a pull request.
|
|
555
|
+
|
|
556
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.read_json.html
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader
|
|
560
|
+
# kwargs
|
|
561
|
+
chunksize: int | None = None
|
|
562
|
+
compression: str | dict[str, Any] | None = "infer"
|
|
563
|
+
convert_axes: bool | None = None
|
|
564
|
+
convert_dates: bool | list[str] = True
|
|
565
|
+
date_unit: str | None = None
|
|
566
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
567
|
+
dtype_backend: str | None = None
|
|
568
|
+
encoding: str | None = None
|
|
569
|
+
encoding_errors: str | None = "strict"
|
|
570
|
+
engine: str = "ujson"
|
|
571
|
+
keep_default_dates: bool = True
|
|
572
|
+
lines: bool = False
|
|
573
|
+
nrows: int | None = None
|
|
574
|
+
orient: str | None = None
|
|
575
|
+
precise_float: bool = False
|
|
576
|
+
storage_options: dict[str, Any] | None = None
|
|
577
|
+
typ: str = "frame"
|
|
578
|
+
|
|
579
|
+
@classmethod
|
|
580
|
+
def applicable_types(cls) -> Collection[type]:
|
|
581
|
+
return [DATAFRAME_TYPE]
|
|
582
|
+
|
|
583
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
584
|
+
kwargs = {}
|
|
585
|
+
if self.chunksize is not None:
|
|
586
|
+
kwargs["chunksize"] = self.chunksize
|
|
587
|
+
if self.compression is not None:
|
|
588
|
+
kwargs["compression"] = self.compression
|
|
589
|
+
if self.convert_axes is not None:
|
|
590
|
+
kwargs["convert_axes"] = self.convert_axes
|
|
591
|
+
if self.convert_dates is not None:
|
|
592
|
+
kwargs["convert_dates"] = self.convert_dates
|
|
593
|
+
if self.date_unit is not None:
|
|
594
|
+
kwargs["date_unit"] = self.date_unit
|
|
595
|
+
if self.dtype is not None:
|
|
596
|
+
kwargs["dtype"] = self.dtype
|
|
597
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
598
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
599
|
+
if self.encoding is not None:
|
|
600
|
+
kwargs["encoding"] = self.encoding
|
|
601
|
+
if self.encoding_errors is not None:
|
|
602
|
+
kwargs["encoding_errors"] = self.encoding_errors
|
|
603
|
+
if self.engine is not None:
|
|
604
|
+
kwargs["engine"] = self.engine
|
|
605
|
+
if self.keep_default_dates is not None:
|
|
606
|
+
kwargs["keep_default_dates"] = self.keep_default_dates
|
|
607
|
+
if self.lines is not None:
|
|
608
|
+
kwargs["lines"] = self.lines
|
|
609
|
+
if self.nrows is not None:
|
|
610
|
+
kwargs["nrows"] = self.nrows
|
|
611
|
+
if self.orient is not None:
|
|
612
|
+
kwargs["orient"] = self.orient
|
|
613
|
+
if self.precise_float is not None:
|
|
614
|
+
kwargs["precise_float"] = self.precise_float
|
|
615
|
+
if self.storage_options is not None:
|
|
616
|
+
kwargs["storage_options"] = self.storage_options
|
|
617
|
+
if self.typ is not None:
|
|
618
|
+
kwargs["typ"] = self.typ
|
|
619
|
+
return kwargs
|
|
620
|
+
|
|
621
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
622
|
+
df = pd.read_json(self.filepath_or_buffer, **self._get_loading_kwargs())
|
|
623
|
+
metadata = utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, df)
|
|
624
|
+
return df, metadata
|
|
625
|
+
|
|
626
|
+
@classmethod
|
|
627
|
+
def name(cls) -> str:
|
|
628
|
+
return "json"
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
@dataclasses.dataclass
|
|
632
|
+
class PandasJsonWriter(DataSaver):
|
|
633
|
+
"""Class specifically to handle saving JSON files/buffers with Pandas.
|
|
634
|
+
|
|
635
|
+
Disclaimer: We're exposing all the *current* params from the Pandas DataFrame.to_json method.
|
|
636
|
+
Some of these params may get deprecated or new params may be introduced. In the event that
|
|
637
|
+
the params/kwargs below become outdated, please raise an issue or submit a pull request.
|
|
638
|
+
|
|
639
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader
|
|
643
|
+
# kwargs
|
|
644
|
+
compression: str = "infer"
|
|
645
|
+
date_format: str = "epoch"
|
|
646
|
+
date_unit: str = "ms"
|
|
647
|
+
default_handler: Callable[[Any], JSONSerializable] | None = None
|
|
648
|
+
double_precision: int = 10
|
|
649
|
+
force_ascii: bool = True
|
|
650
|
+
index: bool | None = None
|
|
651
|
+
indent: int = 0
|
|
652
|
+
lines: bool = False
|
|
653
|
+
mode: str = "w"
|
|
654
|
+
orient: str | None = None
|
|
655
|
+
storage_options: dict[str, Any] | None = None
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def applicable_types(cls) -> Collection[type]:
|
|
659
|
+
return [DATAFRAME_TYPE]
|
|
660
|
+
|
|
661
|
+
def _get_saving_kwargs(self):
|
|
662
|
+
kwargs = {}
|
|
663
|
+
if self.compression is not None:
|
|
664
|
+
kwargs["compression"] = self.compression
|
|
665
|
+
if self.date_format is not None:
|
|
666
|
+
kwargs["date_format"] = self.date_format
|
|
667
|
+
if self.date_unit is not None:
|
|
668
|
+
kwargs["date_unit"] = self.date_unit
|
|
669
|
+
if self.default_handler is not None:
|
|
670
|
+
kwargs["default_handler"] = self.default_handler
|
|
671
|
+
if self.double_precision is not None:
|
|
672
|
+
kwargs["double_precision"] = self.double_precision
|
|
673
|
+
if self.force_ascii is not None:
|
|
674
|
+
kwargs["force_ascii"] = self.force_ascii
|
|
675
|
+
if self.index is not None:
|
|
676
|
+
kwargs["index"] = self.index
|
|
677
|
+
if self.indent is not None:
|
|
678
|
+
kwargs["indent"] = self.indent
|
|
679
|
+
if self.lines is not False:
|
|
680
|
+
kwargs["lines"] = self.lines
|
|
681
|
+
if self.mode is not None:
|
|
682
|
+
kwargs["mode"] = self.mode
|
|
683
|
+
if self.orient is not None:
|
|
684
|
+
kwargs["orient"] = self.orient
|
|
685
|
+
if self.storage_options is not None:
|
|
686
|
+
kwargs["storage_options"] = self.storage_options
|
|
687
|
+
return kwargs
|
|
688
|
+
|
|
689
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
690
|
+
data.to_json(self.filepath_or_buffer, **self._get_saving_kwargs())
|
|
691
|
+
return utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, data)
|
|
692
|
+
|
|
693
|
+
@classmethod
|
|
694
|
+
def name(cls) -> str:
|
|
695
|
+
return "json"
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
@dataclasses.dataclass
|
|
699
|
+
class PandasSqlReader(DataLoader):
|
|
700
|
+
"""Class specifically to handle loading SQL data using Pandas.
|
|
701
|
+
|
|
702
|
+
Disclaimer: We're exposing all the *current* params from the Pandas read_sql method.
|
|
703
|
+
Some of these params may get deprecated or new params may be introduced. In the event that
|
|
704
|
+
the params/kwargs below become outdated, please raise an issue or submit a pull request.
|
|
705
|
+
|
|
706
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.read_sql.html
|
|
707
|
+
Requires optional Pandas dependencies. See https://pandas.pydata.org/docs/getting_started/install.html#sql-databases.
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
query_or_table: str
|
|
711
|
+
db_connection: str | Connection # can pass in SQLAlchemy engine/connection
|
|
712
|
+
# kwarg
|
|
713
|
+
chunksize: int | None = None
|
|
714
|
+
coerce_float: bool = True
|
|
715
|
+
columns: list[str] | None = None
|
|
716
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
717
|
+
dtype_backend: str | None = None
|
|
718
|
+
index_col: str | list[str] | None = None
|
|
719
|
+
params: list | tuple | dict | None = None
|
|
720
|
+
parse_dates: list | dict | None = None
|
|
721
|
+
|
|
722
|
+
@classmethod
|
|
723
|
+
def applicable_types(cls) -> Collection[type]:
|
|
724
|
+
return [DATAFRAME_TYPE]
|
|
725
|
+
|
|
726
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
727
|
+
kwargs = {}
|
|
728
|
+
if self.chunksize is not None:
|
|
729
|
+
kwargs["chunksize"] = self.chunksize
|
|
730
|
+
if self.coerce_float is not None:
|
|
731
|
+
kwargs["coerce_float"] = self.coerce_float
|
|
732
|
+
if self.columns is not None:
|
|
733
|
+
kwargs["columns"] = self.columns
|
|
734
|
+
if self.dtype is not None:
|
|
735
|
+
kwargs["dtype"] = self.dtype
|
|
736
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
737
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
738
|
+
if self.index_col is not None:
|
|
739
|
+
kwargs["index_col"] = self.index_col
|
|
740
|
+
if self.params is not None:
|
|
741
|
+
kwargs["params"] = self.params
|
|
742
|
+
if self.parse_dates is not None:
|
|
743
|
+
kwargs["parse_dates"] = self.parse_dates
|
|
744
|
+
return kwargs
|
|
745
|
+
|
|
746
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
747
|
+
df = pd.read_sql(self.query_or_table, self.db_connection, **self._get_loading_kwargs())
|
|
748
|
+
sql_metadata = utils.get_sql_metadata(self.query_or_table, df)
|
|
749
|
+
df_metadata = utils.get_dataframe_metadata(df)
|
|
750
|
+
return df, {**sql_metadata, **df_metadata}
|
|
751
|
+
|
|
752
|
+
@classmethod
|
|
753
|
+
def name(cls) -> str:
|
|
754
|
+
return "sql"
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
@dataclasses.dataclass
|
|
758
|
+
class PandasSqlWriter(DataSaver):
|
|
759
|
+
"""Class specifically to handle saving DataFrames to SQL databases using Pandas.
|
|
760
|
+
|
|
761
|
+
Disclaimer: We're exposing all the *current* params from the Pandas DataFrame.to_sql method.
|
|
762
|
+
Some of these params may get deprecated or new params may be introduced. In the event that
|
|
763
|
+
the params/kwargs below become outdated, please raise an issue or submit a pull request.
|
|
764
|
+
|
|
765
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html
|
|
766
|
+
Requires optional Pandas dependencies. See https://pandas.pydata.org/docs/getting_started/install.html#sql-databases.
|
|
767
|
+
"""
|
|
768
|
+
|
|
769
|
+
table_name: str
|
|
770
|
+
db_connection: Any # can pass in SQLAlchemy engine/connection
|
|
771
|
+
# kwargs
|
|
772
|
+
chunksize: int | None = None
|
|
773
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
774
|
+
if_exists: str = "fail"
|
|
775
|
+
index: bool = True
|
|
776
|
+
index_label: IndexLabel | None = None
|
|
777
|
+
method: str | Callable | None = None
|
|
778
|
+
schema: str | None = None
|
|
779
|
+
|
|
780
|
+
@classmethod
|
|
781
|
+
def applicable_types(cls) -> Collection[type]:
|
|
782
|
+
return [DATAFRAME_TYPE]
|
|
783
|
+
|
|
784
|
+
def _get_saving_kwargs(self) -> dict[str, Any]:
|
|
785
|
+
kwargs = {}
|
|
786
|
+
if self.chunksize is not None:
|
|
787
|
+
kwargs["chunksize"] = self.chunksize
|
|
788
|
+
if self.dtype is not None:
|
|
789
|
+
kwargs["dtype"] = self.dtype
|
|
790
|
+
if self.if_exists is not None:
|
|
791
|
+
kwargs["if_exists"] = self.if_exists
|
|
792
|
+
if self.index is not None:
|
|
793
|
+
kwargs["index"] = self.index
|
|
794
|
+
if self.index_label is not None:
|
|
795
|
+
kwargs["index_label"] = self.index_label
|
|
796
|
+
if self.method is not None:
|
|
797
|
+
kwargs["method"] = self.method
|
|
798
|
+
if self.schema is not None:
|
|
799
|
+
kwargs["schema"] = self.schema
|
|
800
|
+
return kwargs
|
|
801
|
+
|
|
802
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
803
|
+
results = data.to_sql(self.table_name, self.db_connection, **self._get_saving_kwargs())
|
|
804
|
+
sql_metadata = utils.get_sql_metadata(self.table_name, results)
|
|
805
|
+
df_metadata = utils.get_dataframe_metadata(data)
|
|
806
|
+
return {**sql_metadata, **df_metadata}
|
|
807
|
+
|
|
808
|
+
@classmethod
|
|
809
|
+
def name(cls) -> str:
|
|
810
|
+
return "sql"
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
@dataclasses.dataclass
|
|
814
|
+
class PandasXmlReader(DataLoader):
|
|
815
|
+
"""Class for loading/reading xml files with Pandas.
|
|
816
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_xml.html
|
|
817
|
+
|
|
818
|
+
Requires `lxml`. See https://pandas.pydata.org/docs/getting_started/install.html#xml
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
path_or_buffer: str | Path | BytesIO | BufferedReader
|
|
822
|
+
# kwargs
|
|
823
|
+
xpath: str | None = "./*"
|
|
824
|
+
namespace: dict[str, str] | None = None
|
|
825
|
+
elems_only: bool | None = False
|
|
826
|
+
attrs_only: bool | None = False
|
|
827
|
+
names: list[str] | None = None
|
|
828
|
+
dtype: dict[str, Any] | None = None
|
|
829
|
+
converters: dict[int | str, Any] | None = None
|
|
830
|
+
parse_dates: bool | list[int | str | list[list] | dict[str, list[int]]] = False
|
|
831
|
+
encoding: str | None = "utf-8"
|
|
832
|
+
parser: str = "lxml"
|
|
833
|
+
stylesheet: str | Path | BytesIO | BufferedReader = None
|
|
834
|
+
iterparse: dict[str, list[str]] | None = None
|
|
835
|
+
compression: str | dict[str, Any] | None = "infer"
|
|
836
|
+
storage_options: dict[str, Any] | None = None
|
|
837
|
+
dtype_backend: str = "numpy_nullable"
|
|
838
|
+
|
|
839
|
+
@classmethod
|
|
840
|
+
def applicable_types(cls) -> Collection[type]:
|
|
841
|
+
return [DATAFRAME_TYPE]
|
|
842
|
+
|
|
843
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
844
|
+
kwargs = {}
|
|
845
|
+
if self.xpath is not None:
|
|
846
|
+
kwargs["xpath"] = self.xpath
|
|
847
|
+
if self.namespace is not None:
|
|
848
|
+
kwargs["namespace"] = self.namespace
|
|
849
|
+
if self.elems_only is not None:
|
|
850
|
+
kwargs["elems_only"] = self.elems_only
|
|
851
|
+
if self.attrs_only is not None:
|
|
852
|
+
kwargs["attrs_only"] = self.attrs_only
|
|
853
|
+
if self.names is not None:
|
|
854
|
+
kwargs["names"] = self.names
|
|
855
|
+
if self.dtype is not None:
|
|
856
|
+
kwargs["dtype"] = self.dtype
|
|
857
|
+
if self.converters is not None:
|
|
858
|
+
kwargs["converters"] = self.converters
|
|
859
|
+
if self.parse_dates is not None:
|
|
860
|
+
kwargs["parse_dates"] = self.parse_dates
|
|
861
|
+
if self.encoding is not None:
|
|
862
|
+
kwargs["encoding"] = self.encoding
|
|
863
|
+
if self.parser is not None:
|
|
864
|
+
kwargs["parser"] = self.parser
|
|
865
|
+
if self.encoding is not None:
|
|
866
|
+
kwargs["encoding"] = self.encoding
|
|
867
|
+
if self.parser is not None:
|
|
868
|
+
kwargs["parser"] = self.parser
|
|
869
|
+
if self.stylesheet is not None:
|
|
870
|
+
kwargs["stylesheet"] = self.stylesheet
|
|
871
|
+
if self.iterparse is not None:
|
|
872
|
+
kwargs["iterparse"] = self.iterparse
|
|
873
|
+
if self.compression is not None:
|
|
874
|
+
kwargs["compression"] = self.compression
|
|
875
|
+
if self.storage_options is not None:
|
|
876
|
+
kwargs["storage_options"] = self.storage_options
|
|
877
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
878
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
879
|
+
return kwargs
|
|
880
|
+
|
|
881
|
+
def load_data(self, type: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
882
|
+
# Loads the data and returns the df and metadata of the xml
|
|
883
|
+
df = pd.read_xml(self.path_or_buffer, **self._get_loading_kwargs())
|
|
884
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path_or_buffer, df)
|
|
885
|
+
return df, metadata
|
|
886
|
+
|
|
887
|
+
@classmethod
|
|
888
|
+
def name(cls) -> str:
|
|
889
|
+
return "xml"
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
@dataclasses.dataclass
|
|
893
|
+
class PandasXmlWriter(DataSaver):
|
|
894
|
+
"""Class specifically to handle saving xml files/buffers with Pandas.
|
|
895
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_xml.html
|
|
896
|
+
|
|
897
|
+
Requires `lxml`. See https://pandas.pydata.org/docs/getting_started/install.html#xml.
|
|
898
|
+
"""
|
|
899
|
+
|
|
900
|
+
path_or_buffer: str | Path | BytesIO | BufferedReader
|
|
901
|
+
# kwargs
|
|
902
|
+
index: bool = True
|
|
903
|
+
root_name: str = "data"
|
|
904
|
+
row_name: str = "row"
|
|
905
|
+
na_rep: str | None = None
|
|
906
|
+
attr_cols: list[str] | None = None
|
|
907
|
+
elems_cols: list[str] | None = None
|
|
908
|
+
namespaces: dict[str, str] | None = None
|
|
909
|
+
prefix: str | None = None
|
|
910
|
+
encoding: str = "utf-8"
|
|
911
|
+
xml_declaration: bool = True
|
|
912
|
+
pretty_print: bool = True
|
|
913
|
+
parser: str = "lxml"
|
|
914
|
+
stylesheet: str | Path | BytesIO | BufferedReader | None = None
|
|
915
|
+
compression: str | dict[str, Any] | None = "infer"
|
|
916
|
+
storage_options: dict[str, Any] | None = None
|
|
917
|
+
|
|
918
|
+
@classmethod
|
|
919
|
+
def applicable_types(cls) -> Collection[type]:
|
|
920
|
+
return [DATAFRAME_TYPE]
|
|
921
|
+
|
|
922
|
+
def _get_saving_kwargs(self):
|
|
923
|
+
kwargs = {}
|
|
924
|
+
if self.index is not None:
|
|
925
|
+
kwargs["index"] = self.index
|
|
926
|
+
if self.root_name is not None:
|
|
927
|
+
kwargs["root_name"] = self.root_name
|
|
928
|
+
if self.row_name is not None:
|
|
929
|
+
kwargs["row_name"] = self.row_name
|
|
930
|
+
if self.na_rep is not None:
|
|
931
|
+
kwargs["na_rep"] = self.na_rep
|
|
932
|
+
if self.attr_cols is not None:
|
|
933
|
+
kwargs["attr_cols"] = self.attr_cols
|
|
934
|
+
if self.elems_cols is not None:
|
|
935
|
+
kwargs["elems_cols"] = self.elems_cols
|
|
936
|
+
if self.namespaces is not None:
|
|
937
|
+
kwargs["namespaces"] = self.namespaces
|
|
938
|
+
if self.prefix is not None:
|
|
939
|
+
kwargs["prefix"] = self.prefix
|
|
940
|
+
if self.encoding is not None:
|
|
941
|
+
kwargs["encoding"] = self.encoding
|
|
942
|
+
if self.xml_declaration is not None:
|
|
943
|
+
kwargs["xml_declaration"] = self.xml_declaration
|
|
944
|
+
if self.pretty_print is not None:
|
|
945
|
+
kwargs["pretty_print"] = self.pretty_print
|
|
946
|
+
if self.parser is not None:
|
|
947
|
+
kwargs["parser"] = self.parser
|
|
948
|
+
if self.stylesheet is not None:
|
|
949
|
+
kwargs["stylesheet"] = self.stylesheet
|
|
950
|
+
if self.compression is not None:
|
|
951
|
+
kwargs["compression"] = self.compression
|
|
952
|
+
if self.storage_options is not None:
|
|
953
|
+
kwargs["storage_options"] = self.storage_options
|
|
954
|
+
return kwargs
|
|
955
|
+
|
|
956
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
957
|
+
data.to_xml(self.path_or_buffer, **self._get_saving_kwargs())
|
|
958
|
+
return utils.get_file_and_dataframe_metadata(self.path_or_buffer, data)
|
|
959
|
+
|
|
960
|
+
@classmethod
|
|
961
|
+
def name(cls) -> str:
|
|
962
|
+
return "xml"
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
@dataclasses.dataclass
|
|
966
|
+
class PandasHtmlReader(DataLoader):
|
|
967
|
+
"""Class for loading/reading xml files with Pandas.
|
|
968
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_html.html
|
|
969
|
+
"""
|
|
970
|
+
|
|
971
|
+
io: str | Path | BytesIO | BufferedReader
|
|
972
|
+
# kwargs
|
|
973
|
+
match: str | None = ".+"
|
|
974
|
+
flavor: str | Sequence | None = None
|
|
975
|
+
header: int | Sequence | None = None
|
|
976
|
+
index_col: int | Sequence | None = None
|
|
977
|
+
skiprows: int | Sequence | slice | None = None
|
|
978
|
+
attrs: dict[str, str] | None = None
|
|
979
|
+
parse_dates: bool | None = None
|
|
980
|
+
thousands: str | None = ","
|
|
981
|
+
encoding: str | None = None
|
|
982
|
+
decimal: str = "."
|
|
983
|
+
converters: dict[Any, Any] | None = None
|
|
984
|
+
na_values: Iterable = None
|
|
985
|
+
keep_default_na: bool = True
|
|
986
|
+
displayed_only: bool = True
|
|
987
|
+
extract_links: Literal["header", "footer", "body", "all"] | None = None
|
|
988
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
989
|
+
storage_options: dict[str, Any] | None = None
|
|
990
|
+
|
|
991
|
+
@classmethod
|
|
992
|
+
def applicable_types(cls) -> Collection[type]:
|
|
993
|
+
return [DATAFRAME_TYPE]
|
|
994
|
+
|
|
995
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
996
|
+
kwargs = {}
|
|
997
|
+
if self.match is not None:
|
|
998
|
+
kwargs["match"] = self.match
|
|
999
|
+
if self.flavor is not None:
|
|
1000
|
+
kwargs["flavor"] = self.flavor
|
|
1001
|
+
if self.header is not None:
|
|
1002
|
+
kwargs["header"] = self.header
|
|
1003
|
+
if self.index_col is not None:
|
|
1004
|
+
kwargs["index_col"] = self.index_col
|
|
1005
|
+
if self.skiprows is not None:
|
|
1006
|
+
kwargs["skiprows"] = self.skiprows
|
|
1007
|
+
if self.attrs is not None:
|
|
1008
|
+
kwargs["attrs"] = self.attrs
|
|
1009
|
+
if self.parse_dates is not None:
|
|
1010
|
+
kwargs["parse_dates"] = self.parse_dates
|
|
1011
|
+
if self.thousands is not None:
|
|
1012
|
+
kwargs["thousands"] = self.thousands
|
|
1013
|
+
if self.encoding is not None:
|
|
1014
|
+
kwargs["encoding"] = self.encoding
|
|
1015
|
+
if self.decimal is not None:
|
|
1016
|
+
kwargs["decimal"] = self.decimal
|
|
1017
|
+
if self.converters is not None:
|
|
1018
|
+
kwargs["converters"] = self.converters
|
|
1019
|
+
if self.na_values is not None:
|
|
1020
|
+
kwargs["na_values"] = self.na_values
|
|
1021
|
+
if self.keep_default_na is not None:
|
|
1022
|
+
kwargs["keep_default_na"] = self.keep_default_na
|
|
1023
|
+
if self.displayed_only is not None:
|
|
1024
|
+
kwargs["displayed_only"] = self.displayed_only
|
|
1025
|
+
if self.extract_links is not None:
|
|
1026
|
+
kwargs["extract_links"] = self.extract_links
|
|
1027
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
1028
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
1029
|
+
if self.storage_options is not None:
|
|
1030
|
+
kwargs["storage_options"] = self.storage_options
|
|
1031
|
+
|
|
1032
|
+
return kwargs
|
|
1033
|
+
|
|
1034
|
+
def load_data(self, type: type) -> tuple[list[DATAFRAME_TYPE], dict[str, Any]]:
|
|
1035
|
+
# Loads the data and returns the df and metadata of the xml
|
|
1036
|
+
df = pd.read_html(self.io, **self._get_loading_kwargs())
|
|
1037
|
+
metadata = utils.get_file_and_dataframe_metadata(self.io, df[0])
|
|
1038
|
+
return df, metadata
|
|
1039
|
+
|
|
1040
|
+
@classmethod
|
|
1041
|
+
def name(cls) -> str:
|
|
1042
|
+
return "html"
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
@dataclasses.dataclass
|
|
1046
|
+
class PandasHtmlWriter(DataSaver):
|
|
1047
|
+
"""Class specifically to handle saving xml files/buffers with Pandas.
|
|
1048
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_html.html#pandas.DataFrame.to_html
|
|
1049
|
+
"""
|
|
1050
|
+
|
|
1051
|
+
buf: str | Path | StringIO | None = None
|
|
1052
|
+
# kwargs
|
|
1053
|
+
columns: list[str] | None = None
|
|
1054
|
+
col_space: str | int | list | dict | None = None
|
|
1055
|
+
header: bool | None = True
|
|
1056
|
+
index: bool | None = True
|
|
1057
|
+
na_rep: str | None = "NaN"
|
|
1058
|
+
formatters: list | tuple | dict | None = None
|
|
1059
|
+
float_format: str | None = None
|
|
1060
|
+
sparsify: bool | None = True
|
|
1061
|
+
index_names: bool | None = True
|
|
1062
|
+
justify: str = None
|
|
1063
|
+
max_rows: int | None = None
|
|
1064
|
+
max_cols: int | None = None
|
|
1065
|
+
show_dimensions: bool = False
|
|
1066
|
+
decimal: str = "."
|
|
1067
|
+
bold_rows: bool = True
|
|
1068
|
+
classes: str | list[str] | tuple | None = None
|
|
1069
|
+
escape: bool | None = True
|
|
1070
|
+
notebook: Literal[True, False] = False
|
|
1071
|
+
border: int = None
|
|
1072
|
+
table_id: str | None = None
|
|
1073
|
+
render_links: bool = False
|
|
1074
|
+
encoding: str | None = "utf-8"
|
|
1075
|
+
|
|
1076
|
+
@classmethod
|
|
1077
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1078
|
+
return [DATAFRAME_TYPE]
|
|
1079
|
+
|
|
1080
|
+
def _get_saving_kwargs(self):
|
|
1081
|
+
kwargs = {}
|
|
1082
|
+
if self.columns is not None:
|
|
1083
|
+
kwargs["columns"] = self.columns
|
|
1084
|
+
if self.col_space is not None:
|
|
1085
|
+
kwargs["col_space"] = self.col_space
|
|
1086
|
+
if self.header is not None:
|
|
1087
|
+
kwargs["header"] = self.header
|
|
1088
|
+
if self.index is not None:
|
|
1089
|
+
kwargs["index"] = self.index
|
|
1090
|
+
if self.na_rep is not None:
|
|
1091
|
+
kwargs["na_rep"] = self.na_rep
|
|
1092
|
+
if self.formatters is not None:
|
|
1093
|
+
kwargs["formatters"] = self.formatters
|
|
1094
|
+
if self.float_format is not None:
|
|
1095
|
+
kwargs["float_format"] = self.float_format
|
|
1096
|
+
if self.sparsify is not None:
|
|
1097
|
+
kwargs["sparsify"] = self.sparsify
|
|
1098
|
+
if self.index_names is not None:
|
|
1099
|
+
kwargs["index_names"] = self.index_names
|
|
1100
|
+
if self.justify is not None:
|
|
1101
|
+
kwargs["justify"] = self.justify
|
|
1102
|
+
if self.max_rows is not None:
|
|
1103
|
+
kwargs["max_rows"] = self.max_rows
|
|
1104
|
+
if self.max_cols is not None:
|
|
1105
|
+
kwargs["max_cols"] = self.max_cols
|
|
1106
|
+
if self.show_dimensions is not None:
|
|
1107
|
+
kwargs["show_dimensions"] = self.show_dimensions
|
|
1108
|
+
if self.decimal is not None:
|
|
1109
|
+
kwargs["decimal"] = self.decimal
|
|
1110
|
+
if self.bold_rows is not None:
|
|
1111
|
+
kwargs["bold_rows"] = self.bold_rows
|
|
1112
|
+
if self.classes is not None:
|
|
1113
|
+
kwargs["classes"] = self.classes
|
|
1114
|
+
if self.escape is not None:
|
|
1115
|
+
kwargs["escape"] = self.escape
|
|
1116
|
+
if self.notebook is not None:
|
|
1117
|
+
kwargs["notebook"] = self.notebook
|
|
1118
|
+
if self.border is not None:
|
|
1119
|
+
kwargs["border"] = self.border
|
|
1120
|
+
if self.table_id is not None:
|
|
1121
|
+
kwargs["table_id"] = self.table_id
|
|
1122
|
+
if self.render_links is not None:
|
|
1123
|
+
kwargs["render_links"] = self.render_links
|
|
1124
|
+
if self.encoding is not None:
|
|
1125
|
+
kwargs["encoding"] = self.encoding
|
|
1126
|
+
|
|
1127
|
+
return kwargs
|
|
1128
|
+
|
|
1129
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
1130
|
+
data.to_html(self.buf, **self._get_saving_kwargs())
|
|
1131
|
+
return utils.get_file_and_dataframe_metadata(self.buf, data)
|
|
1132
|
+
|
|
1133
|
+
@classmethod
|
|
1134
|
+
def name(cls) -> str:
|
|
1135
|
+
return "html"
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
@dataclasses.dataclass
|
|
1139
|
+
class PandasStataReader(DataLoader):
|
|
1140
|
+
"""Class for loading/reading xml files with Pandas.
|
|
1141
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_stata.html#pandas.read_stata
|
|
1142
|
+
"""
|
|
1143
|
+
|
|
1144
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader
|
|
1145
|
+
# kwargs
|
|
1146
|
+
convert_dates: bool = True
|
|
1147
|
+
convert_categoricals: bool = True
|
|
1148
|
+
index_col: str | None = None
|
|
1149
|
+
convert_missing: bool = False
|
|
1150
|
+
preserve_dtypes: bool = True
|
|
1151
|
+
columns: Sequence | None = None
|
|
1152
|
+
order_categoricals: bool = True
|
|
1153
|
+
chunksize: int | None = None
|
|
1154
|
+
iterator: bool = False
|
|
1155
|
+
compression: dict[str, Any] | Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] = (
|
|
1156
|
+
"infer"
|
|
1157
|
+
)
|
|
1158
|
+
storage_options: dict[str, Any] | None = None
|
|
1159
|
+
|
|
1160
|
+
@classmethod
|
|
1161
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1162
|
+
return [DATAFRAME_TYPE]
|
|
1163
|
+
|
|
1164
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1165
|
+
kwargs = {}
|
|
1166
|
+
if self.convert_dates is not None:
|
|
1167
|
+
kwargs["convert_dates"] = self.convert_dates
|
|
1168
|
+
if self.convert_categoricals is not None:
|
|
1169
|
+
kwargs["convert_categoricals"] = self.convert_categoricals
|
|
1170
|
+
if self.index_col is not None:
|
|
1171
|
+
kwargs["index_col"] = self.index_col
|
|
1172
|
+
if self.convert_missing is not None:
|
|
1173
|
+
kwargs["convert_missing"] = self.convert_missing
|
|
1174
|
+
if self.preserve_dtypes is not None:
|
|
1175
|
+
kwargs["preserve_dtypes"] = self.preserve_dtypes
|
|
1176
|
+
if self.columns is not None:
|
|
1177
|
+
kwargs["columns"] = self.columns
|
|
1178
|
+
if self.order_categoricals is not None:
|
|
1179
|
+
kwargs["order_categoricals"] = self.order_categoricals
|
|
1180
|
+
if self.chunksize is not None:
|
|
1181
|
+
kwargs["chunksize"] = self.chunksize
|
|
1182
|
+
if self.iterator is not None:
|
|
1183
|
+
kwargs["iterator"] = self.iterator
|
|
1184
|
+
if self.compression is not None:
|
|
1185
|
+
kwargs["compression"] = self.compression
|
|
1186
|
+
if self.storage_options is not None:
|
|
1187
|
+
kwargs["storage_options"] = self.storage_options
|
|
1188
|
+
|
|
1189
|
+
return kwargs
|
|
1190
|
+
|
|
1191
|
+
def load_data(self, type: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1192
|
+
# Loads the data and returns the df and metadata of the xml
|
|
1193
|
+
df = pd.read_stata(self.filepath_or_buffer, **self._get_loading_kwargs())
|
|
1194
|
+
metadata = utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, df)
|
|
1195
|
+
return df, metadata
|
|
1196
|
+
|
|
1197
|
+
@classmethod
|
|
1198
|
+
def name(cls) -> str:
|
|
1199
|
+
return "stata"
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
@dataclasses.dataclass
|
|
1203
|
+
class PandasStataWriter(DataSaver):
|
|
1204
|
+
"""Class specifically to handle saving xml files/buffers with Pandas.
|
|
1205
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_stata.html
|
|
1206
|
+
"""
|
|
1207
|
+
|
|
1208
|
+
path: str | Path | BufferedReader = None
|
|
1209
|
+
# kwargs
|
|
1210
|
+
convert_dates: dict[Hashable, str] | None = None
|
|
1211
|
+
write_index: bool = True
|
|
1212
|
+
byteorder: str | None = None
|
|
1213
|
+
time_stamp: datetime | None = None
|
|
1214
|
+
data_label: str | None = None
|
|
1215
|
+
variable_labels: dict[Hashable, str] | None = None
|
|
1216
|
+
version: Literal[114, 117, 118, 119] = 114
|
|
1217
|
+
convert_strl: str | None = None
|
|
1218
|
+
compression: dict[str, Any] | Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] = (
|
|
1219
|
+
"infer"
|
|
1220
|
+
)
|
|
1221
|
+
storage_options: dict[str, Any] | None = None
|
|
1222
|
+
value_labels: dict[Hashable, str] | None = None
|
|
1223
|
+
|
|
1224
|
+
@classmethod
|
|
1225
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1226
|
+
return [DATAFRAME_TYPE]
|
|
1227
|
+
|
|
1228
|
+
def _get_saving_kwargs(self):
|
|
1229
|
+
kwargs = {}
|
|
1230
|
+
if self.convert_dates is not None:
|
|
1231
|
+
kwargs["convert_dates"] = self.convert_dates
|
|
1232
|
+
if self.write_index is not None:
|
|
1233
|
+
kwargs["write_index"] = self.write_index
|
|
1234
|
+
if self.byteorder is not None:
|
|
1235
|
+
kwargs["byteorder"] = self.byteorder
|
|
1236
|
+
if self.time_stamp is not None:
|
|
1237
|
+
kwargs["time_stamp"] = self.time_stamp
|
|
1238
|
+
if self.data_label is not None:
|
|
1239
|
+
kwargs["data_label"] = self.data_label
|
|
1240
|
+
if self.variable_labels is not None:
|
|
1241
|
+
kwargs["variable_labels"] = self.variable_labels
|
|
1242
|
+
if self.version is not None:
|
|
1243
|
+
kwargs["version"] = self.version
|
|
1244
|
+
if self.convert_strl is not None and self.version == 117:
|
|
1245
|
+
kwargs["convert_strl"] = self.convert_strl
|
|
1246
|
+
if self.compression is not None:
|
|
1247
|
+
kwargs["compression"] = self.compression
|
|
1248
|
+
if self.storage_options is not None:
|
|
1249
|
+
kwargs["storage_options"] = self.storage_options
|
|
1250
|
+
if self.value_labels is not None:
|
|
1251
|
+
kwargs["value_labels"] = self.value_labels
|
|
1252
|
+
|
|
1253
|
+
return kwargs
|
|
1254
|
+
|
|
1255
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
1256
|
+
data.to_stata(self.path, **self._get_saving_kwargs())
|
|
1257
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
1258
|
+
|
|
1259
|
+
@classmethod
|
|
1260
|
+
def name(cls) -> str:
|
|
1261
|
+
return "stata"
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
@dataclasses.dataclass
|
|
1265
|
+
class PandasFeatherReader(DataLoader):
|
|
1266
|
+
"""Class for loading/reading feather files with Pandas.
|
|
1267
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_feather.html
|
|
1268
|
+
"""
|
|
1269
|
+
|
|
1270
|
+
path: str | Path | BytesIO | BufferedReader
|
|
1271
|
+
# kwargs
|
|
1272
|
+
columns: Sequence | None = None
|
|
1273
|
+
use_threads: bool = True
|
|
1274
|
+
storage_options: dict[str, Any] | None = None
|
|
1275
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
1276
|
+
|
|
1277
|
+
@classmethod
|
|
1278
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1279
|
+
return [DATAFRAME_TYPE]
|
|
1280
|
+
|
|
1281
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1282
|
+
kwargs = {}
|
|
1283
|
+
if self.columns is not None:
|
|
1284
|
+
kwargs["columns"] = self.columns
|
|
1285
|
+
if self.use_threads is not None:
|
|
1286
|
+
kwargs["use_threads"] = self.use_threads
|
|
1287
|
+
if self.storage_options is not None:
|
|
1288
|
+
kwargs["storage_options"] = self.storage_options
|
|
1289
|
+
if Version(pd.__version__) >= Version("2.0") and self.dtype_backend is not None:
|
|
1290
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
1291
|
+
|
|
1292
|
+
return kwargs
|
|
1293
|
+
|
|
1294
|
+
def load_data(self, type: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1295
|
+
# Loads the data and returns the df and metadata of the xml
|
|
1296
|
+
df = pd.read_feather(self.path, **self._get_loading_kwargs())
|
|
1297
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
1298
|
+
return df, metadata
|
|
1299
|
+
|
|
1300
|
+
@classmethod
|
|
1301
|
+
def name(cls) -> str:
|
|
1302
|
+
return "feather"
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
@dataclasses.dataclass
|
|
1306
|
+
class PandasFeatherWriter(DataSaver):
|
|
1307
|
+
"""Class specifically to handle saving xml files/buffers with Pandas.
|
|
1308
|
+
Should map to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_feather.html
|
|
1309
|
+
Additional Parameters passed to: https://arrow.apache.org/docs/python/generated/pyarrow.feather.write_feather.html#pyarrow.feather.write_feather
|
|
1310
|
+
|
|
1311
|
+
Requires `lz4` https://pypi.org/project/lz4/
|
|
1312
|
+
"""
|
|
1313
|
+
|
|
1314
|
+
path: str | Path | BytesIO | BufferedReader
|
|
1315
|
+
# kwargs
|
|
1316
|
+
dest: str | None = None
|
|
1317
|
+
compression: Literal["zstd", "lz4", "uncompressed"] = None
|
|
1318
|
+
compression_level: int | None = None
|
|
1319
|
+
chunksize: int | None = None
|
|
1320
|
+
version: int | None = 2
|
|
1321
|
+
|
|
1322
|
+
@classmethod
|
|
1323
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1324
|
+
return [DATAFRAME_TYPE]
|
|
1325
|
+
|
|
1326
|
+
def _get_saving_kwargs(self):
|
|
1327
|
+
kwargs = {}
|
|
1328
|
+
if self.dest is not None:
|
|
1329
|
+
kwargs["dest"] = self.dest
|
|
1330
|
+
if self.compression is not None:
|
|
1331
|
+
kwargs["compression"] = self.compression
|
|
1332
|
+
if self.compression_level is not None:
|
|
1333
|
+
kwargs["compression_level"] = self.compression_level
|
|
1334
|
+
if self.chunksize is not None:
|
|
1335
|
+
kwargs["chunksize"] = self.chunksize
|
|
1336
|
+
if self.version is not None:
|
|
1337
|
+
kwargs["version"] = self.version
|
|
1338
|
+
|
|
1339
|
+
return kwargs
|
|
1340
|
+
|
|
1341
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
1342
|
+
data.to_feather(self.path, **self._get_saving_kwargs())
|
|
1343
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
1344
|
+
|
|
1345
|
+
@classmethod
|
|
1346
|
+
def name(cls) -> str:
|
|
1347
|
+
return "feather"
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
@dataclasses.dataclass
|
|
1351
|
+
class PandasORCReader(DataLoader):
|
|
1352
|
+
"""
|
|
1353
|
+
Class that handles reading ORC files and output a pandas DataFrame
|
|
1354
|
+
Maps to: https://pandas.pydata.org/docs/reference/api/pandas.read_orc.html#pandas.read_orc
|
|
1355
|
+
"""
|
|
1356
|
+
|
|
1357
|
+
path: str | Path | BytesIO | BufferedReader
|
|
1358
|
+
# kwargs
|
|
1359
|
+
columns: list[str] | None = None
|
|
1360
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
1361
|
+
filesystem: FILESYSTEM_TYPE | None = None
|
|
1362
|
+
|
|
1363
|
+
@classmethod
|
|
1364
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1365
|
+
return [DATAFRAME_TYPE]
|
|
1366
|
+
|
|
1367
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1368
|
+
kwargs = {}
|
|
1369
|
+
if self.columns is not None:
|
|
1370
|
+
kwargs["columns"] = self.columns
|
|
1371
|
+
if self.dtype_backend is not None:
|
|
1372
|
+
kwargs["dtype_backend"] = self.dtype_backend
|
|
1373
|
+
if self.filesystem is not None:
|
|
1374
|
+
kwargs["filesystem"] = self.filesystem
|
|
1375
|
+
|
|
1376
|
+
return kwargs
|
|
1377
|
+
|
|
1378
|
+
def load_data(self, type: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1379
|
+
# Loads the data and returns the df and metadata of the orc
|
|
1380
|
+
df = pd.read_orc(self.path, **self._get_loading_kwargs())
|
|
1381
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
1382
|
+
return df, metadata
|
|
1383
|
+
|
|
1384
|
+
@classmethod
|
|
1385
|
+
def name(cls) -> str:
|
|
1386
|
+
return "orc"
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
@dataclasses.dataclass
|
|
1390
|
+
class PandasORCWriter(DataSaver):
|
|
1391
|
+
"""
|
|
1392
|
+
Class that handles writing DataFrames to ORC files.
|
|
1393
|
+
Maps to: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_orc.html
|
|
1394
|
+
"""
|
|
1395
|
+
|
|
1396
|
+
path: str | Path | BytesIO | BufferedReader
|
|
1397
|
+
# kwargs
|
|
1398
|
+
engine: Literal["pyarrow"] = "pyarrow"
|
|
1399
|
+
index: bool | None = None
|
|
1400
|
+
engine_kwargs: dict[str, Any] | None | None = None
|
|
1401
|
+
|
|
1402
|
+
@classmethod
|
|
1403
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1404
|
+
return [DATAFRAME_TYPE]
|
|
1405
|
+
|
|
1406
|
+
def _get_saving_kwargs(self):
|
|
1407
|
+
kwargs = {}
|
|
1408
|
+
if self.engine is not None:
|
|
1409
|
+
kwargs["engine"] = self.engine
|
|
1410
|
+
if self.index is not None:
|
|
1411
|
+
kwargs["index"] = self.index
|
|
1412
|
+
if self.engine_kwargs is not None:
|
|
1413
|
+
kwargs["engine_kwargs"] = self.engine_kwargs
|
|
1414
|
+
|
|
1415
|
+
return kwargs
|
|
1416
|
+
|
|
1417
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
1418
|
+
data.to_orc(self.path, **self._get_saving_kwargs())
|
|
1419
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
1420
|
+
|
|
1421
|
+
@classmethod
|
|
1422
|
+
def name(cls) -> str:
|
|
1423
|
+
return "orc"
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
@dataclasses.dataclass
|
|
1427
|
+
class PandasExcelReader(DataLoader):
|
|
1428
|
+
"""Class for reading Excel files and output a pandas DataFrame.
|
|
1429
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html
|
|
1430
|
+
"""
|
|
1431
|
+
|
|
1432
|
+
path: str | Path | BytesIO | BufferedReader = None
|
|
1433
|
+
# kwargs:
|
|
1434
|
+
# inspect.get_type_hints doesn't work with type aliases,
|
|
1435
|
+
# which are used in pandas.read_excel.
|
|
1436
|
+
# So we have to list all the arguments in plain code.
|
|
1437
|
+
sheet_name: str | int | list[int | str] | None = 0
|
|
1438
|
+
header: int | Sequence | None = 0
|
|
1439
|
+
names: Sequence | None = None
|
|
1440
|
+
index_col: int | str | Sequence | None = None
|
|
1441
|
+
usecols: int | str | Sequence | Sequence | Callable[[str], bool] | None = None
|
|
1442
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
1443
|
+
engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = None
|
|
1444
|
+
converters: dict[str, Callable] | dict[int, Callable] | None = None
|
|
1445
|
+
true_values: Iterable | None = None
|
|
1446
|
+
false_values: Iterable | None = None
|
|
1447
|
+
skiprows: Sequence | int | Callable[[int], object] | None = None
|
|
1448
|
+
nrows: int | None = None
|
|
1449
|
+
na_values = None # in pandas.read_excel there are not type hints for na_values
|
|
1450
|
+
keep_default_na: bool = True
|
|
1451
|
+
na_filter: bool = True
|
|
1452
|
+
verbose: bool | None = None
|
|
1453
|
+
parse_dates: list[int | str] | dict[str, list[int | str]] | bool = False
|
|
1454
|
+
# date_parser: Optional[Callable] # date_parser is deprecated since pandas=2.0.0
|
|
1455
|
+
date_format: dict[Hashable, str] | str | None = None
|
|
1456
|
+
thousands: str | None = None
|
|
1457
|
+
decimal: str = "."
|
|
1458
|
+
comment: str | None = None
|
|
1459
|
+
skipfooter: int = 0
|
|
1460
|
+
storage_options: dict[str, Any] | None = None
|
|
1461
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
1462
|
+
engine_kwargs: dict[str, Any] | None = None
|
|
1463
|
+
|
|
1464
|
+
@classmethod
|
|
1465
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1466
|
+
# Returns type for which data loader is available
|
|
1467
|
+
return [DATAFRAME_TYPE]
|
|
1468
|
+
|
|
1469
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1470
|
+
# Puts kwargs in a dict
|
|
1471
|
+
kwargs = dataclasses.asdict(self)
|
|
1472
|
+
|
|
1473
|
+
# path corresponds to 'io' argument of pandas.read_excel,
|
|
1474
|
+
# but we send it separately
|
|
1475
|
+
del kwargs["path"]
|
|
1476
|
+
|
|
1477
|
+
# engine_kwargs appeared only in pandas >= 2.1
|
|
1478
|
+
# For compatibility with pandas 2.0 we remove engine_kwargs from kwargs if it's empty.
|
|
1479
|
+
if kwargs["engine_kwargs"] is None:
|
|
1480
|
+
del kwargs["engine_kwargs"]
|
|
1481
|
+
|
|
1482
|
+
return kwargs
|
|
1483
|
+
|
|
1484
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1485
|
+
# Loads the data and returns the df and metadata of the excel file
|
|
1486
|
+
df = pd.read_excel(self.path, **self._get_loading_kwargs())
|
|
1487
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
1488
|
+
return df, metadata
|
|
1489
|
+
|
|
1490
|
+
@classmethod
|
|
1491
|
+
def name(cls) -> str:
|
|
1492
|
+
return "excel"
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
@dataclasses.dataclass
|
|
1496
|
+
class PandasExcelWriter(DataSaver):
|
|
1497
|
+
"""Class that handles saving Excel files with pandas.
|
|
1498
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.ExcelWriter.html
|
|
1499
|
+
Additional parameters passed to https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_excel.html
|
|
1500
|
+
"""
|
|
1501
|
+
|
|
1502
|
+
path: str | Path | BytesIO
|
|
1503
|
+
# kwargs:
|
|
1504
|
+
# inspect.get_type_hints doesn't work with type aliases,
|
|
1505
|
+
# which are used in pandas.DataFrame.to_excel.
|
|
1506
|
+
# So we have to list all the arguments in plain code
|
|
1507
|
+
sheet_name: str = "Sheet1"
|
|
1508
|
+
na_rep: str = ""
|
|
1509
|
+
float_format: str | None = None
|
|
1510
|
+
columns: Sequence | None = None
|
|
1511
|
+
header: Sequence | bool = True
|
|
1512
|
+
index: bool = True
|
|
1513
|
+
index_label: IndexLabel | None = None
|
|
1514
|
+
startrow: int = 0
|
|
1515
|
+
startcol: int = 0
|
|
1516
|
+
engine: Literal["openpyxl", "xlsxwriter"] | None = None
|
|
1517
|
+
merge_cells: bool = True
|
|
1518
|
+
inf_rep: str = "inf"
|
|
1519
|
+
freeze_panes: tuple[int, int] | None = None
|
|
1520
|
+
storage_options: dict[str, Any] | None = None
|
|
1521
|
+
engine_kwargs: dict[str, Any] | None = None
|
|
1522
|
+
mode: Literal["w", "a"] | None = "w"
|
|
1523
|
+
if_sheet_exists: Literal["error", "new", "replace", "overlay"] | None = None
|
|
1524
|
+
datetime_format: str = None
|
|
1525
|
+
date_format: str = None
|
|
1526
|
+
|
|
1527
|
+
@classmethod
|
|
1528
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1529
|
+
return [DATAFRAME_TYPE]
|
|
1530
|
+
|
|
1531
|
+
def _get_saving_kwargs(self) -> dict[str, Any]:
|
|
1532
|
+
# Puts kwargs in a dict
|
|
1533
|
+
kwargs = dataclasses.asdict(self)
|
|
1534
|
+
|
|
1535
|
+
# Pass kwargs to ExcelWriter ONLY for kwargs which appear in both ExcelWriter and .to_excel()
|
|
1536
|
+
writer_kwarg_names = [
|
|
1537
|
+
"date_format",
|
|
1538
|
+
"datetime_format",
|
|
1539
|
+
"if_sheet_exists",
|
|
1540
|
+
"mode",
|
|
1541
|
+
"engine_kwargs",
|
|
1542
|
+
"engine",
|
|
1543
|
+
"storage_options",
|
|
1544
|
+
]
|
|
1545
|
+
|
|
1546
|
+
# path corresponds to 'excel_writer' argument of pandas.DataFrame.to_excel,
|
|
1547
|
+
# but we send it separately
|
|
1548
|
+
del kwargs["path"]
|
|
1549
|
+
|
|
1550
|
+
# engine_kwargs appeared only in pandas >= 2.1
|
|
1551
|
+
# For compatibility with pandas 2.0 we remove engine_kwargs from kwargs if it's empty.
|
|
1552
|
+
if kwargs["engine_kwargs"] is None:
|
|
1553
|
+
del kwargs["engine_kwargs"]
|
|
1554
|
+
writer_kwarg_names.remove("engine_kwargs")
|
|
1555
|
+
|
|
1556
|
+
# seperate kwargs for ExcelWriter and to_excel() invocation
|
|
1557
|
+
writer_kwargs = {k: kwargs[k] for k in writer_kwarg_names}
|
|
1558
|
+
to_excel_kwargs = {k: kwargs[k] for k in (kwargs.keys() - set(writer_kwarg_names))}
|
|
1559
|
+
|
|
1560
|
+
return writer_kwargs, to_excel_kwargs
|
|
1561
|
+
|
|
1562
|
+
def save_data(self, data: DATAFRAME_TYPE) -> dict[str, Any]:
|
|
1563
|
+
writer_kwargs, to_excel_kwargs = self._get_saving_kwargs()
|
|
1564
|
+
|
|
1565
|
+
with pd.ExcelWriter(self.path, **writer_kwargs) as writer:
|
|
1566
|
+
data.to_excel(writer, **to_excel_kwargs)
|
|
1567
|
+
return utils.get_file_and_dataframe_metadata(self.path, data)
|
|
1568
|
+
|
|
1569
|
+
@classmethod
|
|
1570
|
+
def name(cls) -> str:
|
|
1571
|
+
return "excel"
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
@dataclasses.dataclass
|
|
1575
|
+
class PandasTableReader(DataLoader):
|
|
1576
|
+
"""Class for loading/reading table files with Pandas.
|
|
1577
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_table.html
|
|
1578
|
+
"""
|
|
1579
|
+
|
|
1580
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader
|
|
1581
|
+
# kwargs
|
|
1582
|
+
sep: str | None = None
|
|
1583
|
+
delimiter: str | None = None
|
|
1584
|
+
header: int | Sequence | str | None = "infer"
|
|
1585
|
+
names: Sequence | None = None
|
|
1586
|
+
index_col: int | str | Sequence | None = None
|
|
1587
|
+
usecols: Sequence | None = None
|
|
1588
|
+
dtype: Dtype | dict[Hashable, Dtype] | None = None
|
|
1589
|
+
engine: Literal["c", "python", "pyarrow"] | None = None
|
|
1590
|
+
converters: dict[Hashable, Callable] | None = None
|
|
1591
|
+
true_values: Iterable | None = None
|
|
1592
|
+
false_values: Iterable | None = None
|
|
1593
|
+
skipinitialspace: bool = False
|
|
1594
|
+
skiprows: list[int] | int | list[Callable] | None = None
|
|
1595
|
+
skipfooter: int = 0
|
|
1596
|
+
nrows: int | None = None
|
|
1597
|
+
na_values: Hashable | Iterable | dict[Hashable, Iterable] | None = None
|
|
1598
|
+
keep_default_na: bool = True
|
|
1599
|
+
na_filter: bool = True
|
|
1600
|
+
verbose: bool | None = None
|
|
1601
|
+
skip_blank_lines: bool = True
|
|
1602
|
+
parse_dates: list[int | str] | dict[str, list[int | str]] | bool = False
|
|
1603
|
+
infer_datetime_format: bool = False
|
|
1604
|
+
keep_date_col: bool | None = None
|
|
1605
|
+
date_parser: Callable | None = None
|
|
1606
|
+
date_format: str | str | None = None
|
|
1607
|
+
dayfirst: bool = False
|
|
1608
|
+
cache_dates: bool = True
|
|
1609
|
+
iterator: bool = False
|
|
1610
|
+
chunksize: int | None = None
|
|
1611
|
+
compression: str | dict = "infer"
|
|
1612
|
+
thousands: str | None = None
|
|
1613
|
+
decimal: str = "."
|
|
1614
|
+
lineterminator: str | None = None
|
|
1615
|
+
quotechar: str | None = '"'
|
|
1616
|
+
quoting: int = 0
|
|
1617
|
+
doublequote: bool = True
|
|
1618
|
+
escapechar: str | None = None
|
|
1619
|
+
comment: str | None = None
|
|
1620
|
+
encoding: str | None = None
|
|
1621
|
+
encoding_errors: str | None = "strict"
|
|
1622
|
+
dialect: str | None = None
|
|
1623
|
+
on_bad_lines: Literal["error", "warn", "skip"] | Callable = "error"
|
|
1624
|
+
delim_whitespace: bool | None = None
|
|
1625
|
+
low_memory: bool = True
|
|
1626
|
+
memory_map: bool = False
|
|
1627
|
+
float_precision: Literal["high", "legacy", "round_trip"] | None = None
|
|
1628
|
+
storage_options: dict | None = None
|
|
1629
|
+
dtype_backend: Literal["numpy_nullable", "pyarrow"] = "numpy_nullable"
|
|
1630
|
+
|
|
1631
|
+
@classmethod
|
|
1632
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1633
|
+
return [DATAFRAME_TYPE]
|
|
1634
|
+
|
|
1635
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1636
|
+
# Puts kwargs in a dict
|
|
1637
|
+
kwargs = dataclasses.asdict(self)
|
|
1638
|
+
|
|
1639
|
+
# filepath_or_buffer corresponds to 'filepath_or_buffer' argument of pandas.read_table,
|
|
1640
|
+
# but we send it separately
|
|
1641
|
+
del kwargs["filepath_or_buffer"]
|
|
1642
|
+
|
|
1643
|
+
return kwargs
|
|
1644
|
+
|
|
1645
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1646
|
+
# Loads the data and returns the df and metadata of the table
|
|
1647
|
+
df = pd.read_table(self.filepath_or_buffer, **self._get_loading_kwargs())
|
|
1648
|
+
metadata = utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, df)
|
|
1649
|
+
return df, metadata
|
|
1650
|
+
|
|
1651
|
+
@classmethod
|
|
1652
|
+
def name(cls) -> str:
|
|
1653
|
+
return "table"
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
@dataclasses.dataclass
|
|
1657
|
+
class PandasFWFReader(DataLoader):
|
|
1658
|
+
"""Class for loading/reading fixed-width formatted files with Pandas.
|
|
1659
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_fwf.html
|
|
1660
|
+
"""
|
|
1661
|
+
|
|
1662
|
+
filepath_or_buffer: str | Path | BytesIO | BufferedReader
|
|
1663
|
+
# kwargs
|
|
1664
|
+
colspecs: str | list[tuple[int, int]] | tuple[int, int] = "infer"
|
|
1665
|
+
widths: list[int] | None = None
|
|
1666
|
+
infer_nrows: int = 100
|
|
1667
|
+
dtype_backend: Literal["numpy_nullable", "pyarrow"] = "numpy_nullable"
|
|
1668
|
+
|
|
1669
|
+
@classmethod
|
|
1670
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1671
|
+
return [DATAFRAME_TYPE]
|
|
1672
|
+
|
|
1673
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1674
|
+
# Puts kwargs in a dict
|
|
1675
|
+
kwargs = dataclasses.asdict(self)
|
|
1676
|
+
|
|
1677
|
+
# filepath_or_buffer corresponds to 'filepath_or_buffer' argument of pandas.read_fwf,
|
|
1678
|
+
# but we send it separately
|
|
1679
|
+
del kwargs["filepath_or_buffer"]
|
|
1680
|
+
|
|
1681
|
+
return kwargs
|
|
1682
|
+
|
|
1683
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1684
|
+
# Loads the data and returns the df and metadata of the fwf file
|
|
1685
|
+
df = pd.read_fwf(self.filepath_or_buffer, **self._get_loading_kwargs())
|
|
1686
|
+
metadata = utils.get_file_and_dataframe_metadata(self.filepath_or_buffer, df)
|
|
1687
|
+
return df, metadata
|
|
1688
|
+
|
|
1689
|
+
@classmethod
|
|
1690
|
+
def name(cls) -> str:
|
|
1691
|
+
return "fwf"
|
|
1692
|
+
|
|
1693
|
+
|
|
1694
|
+
@dataclasses.dataclass
|
|
1695
|
+
class PandasSPSSReader(DataLoader):
|
|
1696
|
+
"""Class for loading/reading spss files with Pandas.
|
|
1697
|
+
Maps to https://pandas.pydata.org/docs/reference/api/pandas.read_spss.html
|
|
1698
|
+
"""
|
|
1699
|
+
|
|
1700
|
+
path: str | Path
|
|
1701
|
+
# kwargs
|
|
1702
|
+
usecols: list[Hashable] | Callable[[str], bool] | None = None
|
|
1703
|
+
convert_categoricals: bool = True
|
|
1704
|
+
dtype_backend: Literal["pyarrow", "numpy_nullable"] = "numpy_nullable"
|
|
1705
|
+
|
|
1706
|
+
@classmethod
|
|
1707
|
+
def applicable_types(cls) -> Collection[type]:
|
|
1708
|
+
return [DATAFRAME_TYPE]
|
|
1709
|
+
|
|
1710
|
+
def _get_loading_kwargs(self) -> dict[str, Any]:
|
|
1711
|
+
# Puts kwargs in a dict
|
|
1712
|
+
kwargs = dataclasses.asdict(self)
|
|
1713
|
+
|
|
1714
|
+
# path corresponds to 'io' argument of pandas.read_spss,
|
|
1715
|
+
# but we send it separately
|
|
1716
|
+
del kwargs["path"]
|
|
1717
|
+
|
|
1718
|
+
return kwargs
|
|
1719
|
+
|
|
1720
|
+
def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
|
|
1721
|
+
# Loads the data and returns the df and metadata of the spss file
|
|
1722
|
+
df = pd.read_spss(self.path, **self._get_loading_kwargs())
|
|
1723
|
+
metadata = utils.get_file_and_dataframe_metadata(self.path, df)
|
|
1724
|
+
return df, metadata
|
|
1725
|
+
|
|
1726
|
+
@classmethod
|
|
1727
|
+
def name(cls) -> str:
|
|
1728
|
+
return "spss"
|
|
1729
|
+
|
|
1730
|
+
|
|
1731
|
+
def register_data_loaders():
|
|
1732
|
+
"""Function to register the data loaders for this extension."""
|
|
1733
|
+
for loader in [
|
|
1734
|
+
PandasCSVReader,
|
|
1735
|
+
PandasCSVWriter,
|
|
1736
|
+
PandasParquetReader,
|
|
1737
|
+
PandasParquetWriter,
|
|
1738
|
+
PandasPickleReader,
|
|
1739
|
+
PandasPickleWriter,
|
|
1740
|
+
PandasJsonReader,
|
|
1741
|
+
PandasJsonWriter,
|
|
1742
|
+
PandasSqlReader,
|
|
1743
|
+
PandasSqlWriter,
|
|
1744
|
+
PandasXmlReader,
|
|
1745
|
+
PandasXmlWriter,
|
|
1746
|
+
PandasHtmlReader,
|
|
1747
|
+
PandasHtmlWriter,
|
|
1748
|
+
PandasStataReader,
|
|
1749
|
+
PandasStataWriter,
|
|
1750
|
+
PandasFeatherReader,
|
|
1751
|
+
PandasFeatherWriter,
|
|
1752
|
+
PandasORCWriter,
|
|
1753
|
+
PandasORCReader,
|
|
1754
|
+
PandasExcelWriter,
|
|
1755
|
+
PandasExcelReader,
|
|
1756
|
+
PandasTableReader,
|
|
1757
|
+
PandasFWFReader,
|
|
1758
|
+
PandasSPSSReader,
|
|
1759
|
+
]:
|
|
1760
|
+
registry.register_adapter(loader)
|
|
1761
|
+
|
|
1762
|
+
|
|
1763
|
+
register_data_loaders()
|