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.
Files changed (151) hide show
  1. apache_hamilton-1.90.0.dev0.dist-info/METADATA +407 -0
  2. apache_hamilton-1.90.0.dev0.dist-info/RECORD +151 -0
  3. apache_hamilton-1.90.0.dev0.dist-info/WHEEL +4 -0
  4. apache_hamilton-1.90.0.dev0.dist-info/entry_points.txt +9 -0
  5. apache_hamilton-1.90.0.dev0.dist-info/licenses/DISCLAIMER +10 -0
  6. apache_hamilton-1.90.0.dev0.dist-info/licenses/LICENSE +228 -0
  7. apache_hamilton-1.90.0.dev0.dist-info/licenses/NOTICE +5 -0
  8. hamilton/__init__.py +24 -0
  9. hamilton/ad_hoc_utils.py +132 -0
  10. hamilton/async_driver.py +465 -0
  11. hamilton/base.py +466 -0
  12. hamilton/caching/__init__.py +16 -0
  13. hamilton/caching/adapter.py +1475 -0
  14. hamilton/caching/cache_key.py +70 -0
  15. hamilton/caching/fingerprinting.py +287 -0
  16. hamilton/caching/stores/__init__.py +16 -0
  17. hamilton/caching/stores/base.py +242 -0
  18. hamilton/caching/stores/file.py +140 -0
  19. hamilton/caching/stores/memory.py +297 -0
  20. hamilton/caching/stores/sqlite.py +282 -0
  21. hamilton/caching/stores/utils.py +40 -0
  22. hamilton/cli/__init__.py +16 -0
  23. hamilton/cli/__main__.py +328 -0
  24. hamilton/cli/commands.py +126 -0
  25. hamilton/cli/logic.py +338 -0
  26. hamilton/common/__init__.py +76 -0
  27. hamilton/contrib/__init__.py +41 -0
  28. hamilton/data_quality/__init__.py +16 -0
  29. hamilton/data_quality/base.py +198 -0
  30. hamilton/data_quality/default_validators.py +560 -0
  31. hamilton/data_quality/pandera_validators.py +121 -0
  32. hamilton/dataflows/__init__.py +726 -0
  33. hamilton/dataflows/template/README.md +25 -0
  34. hamilton/dataflows/template/__init__.py +54 -0
  35. hamilton/dataflows/template/author.md +28 -0
  36. hamilton/dataflows/template/requirements.txt +0 -0
  37. hamilton/dataflows/template/tags.json +7 -0
  38. hamilton/dataflows/template/valid_configs.jsonl +1 -0
  39. hamilton/dev_utils/__init__.py +16 -0
  40. hamilton/dev_utils/deprecation.py +204 -0
  41. hamilton/driver.py +2112 -0
  42. hamilton/execution/__init__.py +16 -0
  43. hamilton/execution/debugging_utils.py +56 -0
  44. hamilton/execution/executors.py +502 -0
  45. hamilton/execution/graph_functions.py +421 -0
  46. hamilton/execution/grouping.py +430 -0
  47. hamilton/execution/state.py +539 -0
  48. hamilton/experimental/__init__.py +27 -0
  49. hamilton/experimental/databackend.py +61 -0
  50. hamilton/experimental/decorators/__init__.py +16 -0
  51. hamilton/experimental/decorators/parameterize_frame.py +233 -0
  52. hamilton/experimental/h_async.py +29 -0
  53. hamilton/experimental/h_cache.py +413 -0
  54. hamilton/experimental/h_dask.py +28 -0
  55. hamilton/experimental/h_databackends.py +174 -0
  56. hamilton/experimental/h_ray.py +28 -0
  57. hamilton/experimental/h_spark.py +32 -0
  58. hamilton/function_modifiers/README +40 -0
  59. hamilton/function_modifiers/__init__.py +121 -0
  60. hamilton/function_modifiers/adapters.py +900 -0
  61. hamilton/function_modifiers/base.py +859 -0
  62. hamilton/function_modifiers/configuration.py +310 -0
  63. hamilton/function_modifiers/delayed.py +202 -0
  64. hamilton/function_modifiers/dependencies.py +246 -0
  65. hamilton/function_modifiers/expanders.py +1230 -0
  66. hamilton/function_modifiers/macros.py +1634 -0
  67. hamilton/function_modifiers/metadata.py +434 -0
  68. hamilton/function_modifiers/recursive.py +908 -0
  69. hamilton/function_modifiers/validation.py +289 -0
  70. hamilton/function_modifiers_base.py +31 -0
  71. hamilton/graph.py +1153 -0
  72. hamilton/graph_types.py +264 -0
  73. hamilton/graph_utils.py +41 -0
  74. hamilton/htypes.py +450 -0
  75. hamilton/io/__init__.py +32 -0
  76. hamilton/io/data_adapters.py +216 -0
  77. hamilton/io/default_data_loaders.py +224 -0
  78. hamilton/io/materialization.py +500 -0
  79. hamilton/io/utils.py +158 -0
  80. hamilton/lifecycle/__init__.py +67 -0
  81. hamilton/lifecycle/api.py +833 -0
  82. hamilton/lifecycle/base.py +1130 -0
  83. hamilton/lifecycle/default.py +802 -0
  84. hamilton/log_setup.py +47 -0
  85. hamilton/models.py +92 -0
  86. hamilton/node.py +449 -0
  87. hamilton/plugins/README.md +48 -0
  88. hamilton/plugins/__init__.py +16 -0
  89. hamilton/plugins/dask_extensions.py +47 -0
  90. hamilton/plugins/dlt_extensions.py +161 -0
  91. hamilton/plugins/geopandas_extensions.py +49 -0
  92. hamilton/plugins/h_dask.py +331 -0
  93. hamilton/plugins/h_ddog.py +522 -0
  94. hamilton/plugins/h_diskcache.py +163 -0
  95. hamilton/plugins/h_experiments/__init__.py +22 -0
  96. hamilton/plugins/h_experiments/__main__.py +62 -0
  97. hamilton/plugins/h_experiments/cache.py +39 -0
  98. hamilton/plugins/h_experiments/data_model.py +68 -0
  99. hamilton/plugins/h_experiments/hook.py +219 -0
  100. hamilton/plugins/h_experiments/server.py +375 -0
  101. hamilton/plugins/h_kedro.py +152 -0
  102. hamilton/plugins/h_logging.py +454 -0
  103. hamilton/plugins/h_mcp/__init__.py +28 -0
  104. hamilton/plugins/h_mcp/__main__.py +33 -0
  105. hamilton/plugins/h_mcp/_helpers.py +129 -0
  106. hamilton/plugins/h_mcp/_templates.py +417 -0
  107. hamilton/plugins/h_mcp/server.py +328 -0
  108. hamilton/plugins/h_mlflow.py +335 -0
  109. hamilton/plugins/h_narwhals.py +134 -0
  110. hamilton/plugins/h_openlineage.py +400 -0
  111. hamilton/plugins/h_opentelemetry.py +167 -0
  112. hamilton/plugins/h_pandas.py +257 -0
  113. hamilton/plugins/h_pandera.py +117 -0
  114. hamilton/plugins/h_polars.py +304 -0
  115. hamilton/plugins/h_polars_lazyframe.py +282 -0
  116. hamilton/plugins/h_pyarrow.py +56 -0
  117. hamilton/plugins/h_pydantic.py +127 -0
  118. hamilton/plugins/h_ray.py +242 -0
  119. hamilton/plugins/h_rich.py +142 -0
  120. hamilton/plugins/h_schema.py +493 -0
  121. hamilton/plugins/h_slack.py +100 -0
  122. hamilton/plugins/h_spark.py +1380 -0
  123. hamilton/plugins/h_threadpool.py +125 -0
  124. hamilton/plugins/h_tqdm.py +122 -0
  125. hamilton/plugins/h_vaex.py +129 -0
  126. hamilton/plugins/huggingface_extensions.py +236 -0
  127. hamilton/plugins/ibis_extensions.py +93 -0
  128. hamilton/plugins/jupyter_magic.py +622 -0
  129. hamilton/plugins/kedro_extensions.py +117 -0
  130. hamilton/plugins/lightgbm_extensions.py +99 -0
  131. hamilton/plugins/matplotlib_extensions.py +108 -0
  132. hamilton/plugins/mlflow_extensions.py +216 -0
  133. hamilton/plugins/numpy_extensions.py +105 -0
  134. hamilton/plugins/pandas_extensions.py +1763 -0
  135. hamilton/plugins/plotly_extensions.py +150 -0
  136. hamilton/plugins/polars_extensions.py +71 -0
  137. hamilton/plugins/polars_implementations.py +25 -0
  138. hamilton/plugins/polars_lazyframe_extensions.py +302 -0
  139. hamilton/plugins/polars_post_1_0_0_extensions.py +877 -0
  140. hamilton/plugins/polars_pre_1_0_0_extension.py +836 -0
  141. hamilton/plugins/pydantic_extensions.py +98 -0
  142. hamilton/plugins/pyspark_pandas_extensions.py +47 -0
  143. hamilton/plugins/sklearn_plot_extensions.py +129 -0
  144. hamilton/plugins/spark_extensions.py +105 -0
  145. hamilton/plugins/vaex_extensions.py +51 -0
  146. hamilton/plugins/xgboost_extensions.py +91 -0
  147. hamilton/plugins/yaml_extensions.py +89 -0
  148. hamilton/registry.py +254 -0
  149. hamilton/settings.py +18 -0
  150. hamilton/telemetry.py +50 -0
  151. hamilton/version.py +18 -0
@@ -0,0 +1,836 @@
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 dataclasses
19
+ from collections.abc import Collection, Mapping, Sequence
20
+ from io import BytesIO, IOBase, TextIOWrapper
21
+ from pathlib import Path
22
+ from typing import (
23
+ Any,
24
+ BinaryIO,
25
+ Literal,
26
+ TextIO,
27
+ )
28
+
29
+ try:
30
+ from xlsxwriter.workbook import Workbook
31
+ except ImportError:
32
+ Workbook = type
33
+
34
+ try:
35
+ import polars as pl
36
+ except ImportError as e:
37
+ raise NotImplementedError("Polars is not installed.") from e
38
+
39
+
40
+ # for polars <0.16.0 we need to determine whether type_aliases exist.
41
+ has_alias = False
42
+ if hasattr(pl, "type_aliases"):
43
+ has_alias = True
44
+
45
+ # for polars 0.18.0 we need to check what to do.
46
+ if has_alias and hasattr(pl.type_aliases, "CsvEncoding"):
47
+ from polars.type_aliases import CsvEncoding, SchemaDefinition
48
+ else:
49
+ CsvEncoding = type
50
+ if has_alias and hasattr(pl.type_aliases, "CsvQuoteStyle"):
51
+ from polars.type_aliases import CsvQuoteStyle
52
+ else:
53
+ CsvQuoteStyle = type
54
+ if has_alias and hasattr(pl.type_aliases, "IpcCompression"):
55
+ from polars.type_aliases import IpcCompression
56
+ else:
57
+ IpcCompression = type
58
+
59
+ from hamilton import registry
60
+ from hamilton.io import utils
61
+ from hamilton.io.data_adapters import DataLoader, DataSaver
62
+
63
+ DATAFRAME_TYPE = pl.DataFrame
64
+ COLUMN_TYPE = pl.Series
65
+
66
+
67
+ @registry.get_column.register(pl.DataFrame)
68
+ def get_column_polars(df: pl.DataFrame, column_name: str) -> pl.Series:
69
+ return df[column_name]
70
+
71
+
72
+ @registry.fill_with_scalar.register(pl.DataFrame)
73
+ def fill_with_scalar_polars(df: pl.DataFrame, column_name: str, scalar_value: Any) -> pl.DataFrame:
74
+ if not isinstance(scalar_value, pl.Series):
75
+ scalar_value = [scalar_value]
76
+ return df.with_columns(pl.Series(name=column_name, values=scalar_value))
77
+
78
+
79
+ @dataclasses.dataclass
80
+ class PolarsCSVReader(DataLoader):
81
+ """Class specifically to handle loading CSV files with Polars.
82
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_csv.html
83
+ """
84
+
85
+ file: str | TextIO | BytesIO | Path | BinaryIO | bytes
86
+ # kwargs:
87
+ has_header: bool = True
88
+ include_header: bool = True
89
+ columns: Sequence[int] | Sequence[str] = None
90
+ new_columns: Sequence[str] = None
91
+ separator: str = ","
92
+ comment_char: str = None
93
+ quote_char: str = '"'
94
+ skip_rows: int = 0
95
+ dtypes: Mapping[str, Any] | Sequence[Any] = None
96
+ null_values: str | Sequence[str] | dict[str, str] = None
97
+ missing_utf8_is_empty_string: bool = False
98
+ ignore_errors: bool = False
99
+ try_parse_dates: bool = False
100
+ n_threads: int = None
101
+ infer_schema_length: int = 100
102
+ batch_size: int = 8192
103
+ n_rows: int = None
104
+ encoding: CsvEncoding | str = "utf8"
105
+ low_memory: bool = False
106
+ rechunk: bool = True
107
+ use_pyarrow: bool = False
108
+ storage_options: dict[str, Any] = None
109
+ skip_rows_after_header: int = 0
110
+ row_count_name: str = None
111
+ row_count_offset: int = 0
112
+ sample_size: int = 1024
113
+ eol_char: str = "\n"
114
+ raise_if_empty: bool = True
115
+
116
+ @classmethod
117
+ def applicable_types(cls) -> Collection[type]:
118
+ return [DATAFRAME_TYPE]
119
+
120
+ def _get_loading_kwargs(self):
121
+ kwargs = {}
122
+ if self.has_header is not None:
123
+ kwargs["has_header"] = self.has_header
124
+ if self.columns is not None:
125
+ kwargs["columns"] = self.columns
126
+ if self.new_columns is not None:
127
+ kwargs["new_columns"] = self.new_columns
128
+ if self.separator is not None:
129
+ kwargs["separator"] = self.separator
130
+ if self.comment_char is not None:
131
+ kwargs["comment_char"] = self.comment_char
132
+ if self.quote_char is not None:
133
+ kwargs["quote_char"] = self.quote_char
134
+ if self.skip_rows is not None:
135
+ kwargs["skip_rows"] = self.skip_rows
136
+ if self.dtypes is not None:
137
+ kwargs["dtypes"] = self.dtypes
138
+ if self.null_values is not None:
139
+ kwargs["null_values"] = self.null_values
140
+ if self.missing_utf8_is_empty_string is not None:
141
+ kwargs["missing_utf8_is_empty_string"] = self.missing_utf8_is_empty_string
142
+ if self.ignore_errors is not None:
143
+ kwargs["ignore_errors"] = self.ignore_errors
144
+ if self.try_parse_dates is not None:
145
+ kwargs["try_parse_dates"] = self.try_parse_dates
146
+ if self.n_threads is not None:
147
+ kwargs["n_threads"] = self.n_threads
148
+ if self.infer_schema_length is not None:
149
+ kwargs["infer_schema_length"] = self.infer_schema_length
150
+ if self.batch_size is not None:
151
+ kwargs["batch_size"] = self.batch_size
152
+ if self.n_rows is not None:
153
+ kwargs["n_rows"] = self.n_rows
154
+ if self.encoding is not None:
155
+ kwargs["encoding"] = self.encoding
156
+ if self.low_memory is not None:
157
+ kwargs["low_memory"] = self.low_memory
158
+ if self.rechunk is not None:
159
+ kwargs["rechunk"] = self.rechunk
160
+ if self.use_pyarrow is not None:
161
+ kwargs["use_pyarrow"] = self.use_pyarrow
162
+ if self.storage_options is not None:
163
+ kwargs["storage_options"] = self.storage_options
164
+ if self.skip_rows_after_header is not None:
165
+ kwargs["skip_rows_after_header"] = self.skip_rows_after_header
166
+ if self.row_count_name is not None:
167
+ kwargs["row_count_name"] = self.row_count_name
168
+ if self.row_count_offset is not None:
169
+ kwargs["row_count_offset"] = self.row_count_offset
170
+ if self.eol_char is not None:
171
+ kwargs["eol_char"] = self.eol_char
172
+ if self.raise_if_empty is not None:
173
+ kwargs["raise_if_empty"] = self.raise_if_empty
174
+ return kwargs
175
+
176
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
177
+ df = pl.read_csv(self.file, **self._get_loading_kwargs())
178
+
179
+ metadata = utils.get_file_and_dataframe_metadata(self.file, df)
180
+ return df, metadata
181
+
182
+ @classmethod
183
+ def name(cls) -> str:
184
+ return "csv"
185
+
186
+
187
+ @dataclasses.dataclass
188
+ class PolarsCSVWriter(DataSaver):
189
+ """Class specifically to handle saving CSV files with Polars.
190
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_csv.html
191
+ """
192
+
193
+ file: BytesIO | TextIOWrapper | str | Path
194
+ # kwargs:
195
+ has_header: bool = True
196
+ separator: str = ","
197
+ line_terminator: str = "\n"
198
+ quote: str = '"'
199
+ batch_size: int = 1024
200
+ datetime_format: str = None
201
+ date_format: str = None
202
+ time_format: str = None
203
+ float_precision: int = None
204
+ null_value: str = None
205
+ quote_style: CsvQuoteStyle = None
206
+
207
+ @classmethod
208
+ def applicable_types(cls) -> Collection[type]:
209
+ return [DATAFRAME_TYPE, pl.LazyFrame]
210
+
211
+ def _get_saving_kwargs(self):
212
+ kwargs = {}
213
+ if self.separator is not None:
214
+ kwargs["separator"] = self.separator
215
+ if self.has_header is not None:
216
+ kwargs["has_header"] = self.has_header
217
+ if self.separator is not None:
218
+ kwargs["separator"] = self.separator
219
+ if self.line_terminator is not None:
220
+ kwargs["line_terminator"] = self.line_terminator
221
+ if self.quote is not None:
222
+ kwargs["quote"] = self.quote
223
+ if self.batch_size is not None:
224
+ kwargs["batch_size"] = self.batch_size
225
+ if self.datetime_format is not None:
226
+ kwargs["datetime_format"] = self.datetime_format
227
+ if self.date_format is not None:
228
+ kwargs["date_format"] = self.date_format
229
+ if self.time_format is not None:
230
+ kwargs["time_format"] = self.time_format
231
+ if self.float_precision is not None:
232
+ kwargs["float_precision"] = self.float_precision
233
+ if self.null_value is not None:
234
+ kwargs["null_value"] = self.null_value
235
+ if self.quote_style is not None:
236
+ kwargs["quote_style"] = self.quote_style
237
+ return kwargs
238
+
239
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
240
+ if isinstance(data, pl.LazyFrame):
241
+ data = data.collect()
242
+ data.write_csv(self.file, **self._get_saving_kwargs())
243
+ return utils.get_file_and_dataframe_metadata(self.file, data)
244
+
245
+ @classmethod
246
+ def name(cls) -> str:
247
+ return "csv"
248
+
249
+
250
+ @dataclasses.dataclass
251
+ class PolarsParquetReader(DataLoader):
252
+ """Class specifically to handle loading parquet files with polars
253
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_parquet.html
254
+ """
255
+
256
+ file: str | TextIO | BytesIO | Path | BinaryIO | bytes
257
+ # kwargs:
258
+ columns: list[int] | list[str] = None
259
+ n_rows: int = None
260
+ use_pyarrow: bool = False
261
+ memory_map: bool = True
262
+ storage_options: dict[str, Any] = None
263
+ parallel: Any = "auto"
264
+ row_count_name: str = None
265
+ row_count_offset: int = 0
266
+ low_memory: bool = False
267
+ pyarrow_options: dict[str, Any] = None
268
+ use_statistics: bool = True
269
+ rechunk: bool = True
270
+
271
+ @classmethod
272
+ def applicable_types(cls) -> Collection[type]:
273
+ return [DATAFRAME_TYPE]
274
+
275
+ def _get_loading_kwargs(self):
276
+ kwargs = {}
277
+ if self.columns is not None:
278
+ kwargs["columns"] = self.columns
279
+ if self.n_rows is not None:
280
+ kwargs["n_rows"] = self.n_rows
281
+ if self.use_pyarrow is not None:
282
+ kwargs["use_pyarrow"] = self.use_pyarrow
283
+ if self.memory_map is not None:
284
+ kwargs["memory_map"] = self.memory_map
285
+ if self.storage_options is not None:
286
+ kwargs["storage_options"] = self.storage_options
287
+ if self.parallel is not None:
288
+ kwargs["parallel"] = self.parallel
289
+ if self.row_count_name is not None:
290
+ kwargs["row_count_name"] = self.row_count_name
291
+ if self.row_count_offset is not None:
292
+ kwargs["row_count_offset"] = self.row_count_offset
293
+ if self.low_memory is not None:
294
+ kwargs["low_memory"] = self.low_memory
295
+ if self.pyarrow_options is not None:
296
+ kwargs["pyarrow_options"] = self.pyarrow_options
297
+ if self.use_statistics is not None:
298
+ kwargs["use_statistics"] = self.use_statistics
299
+ if self.rechunk is not None:
300
+ kwargs["rechunk"] = self.rechunk
301
+ return kwargs
302
+
303
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
304
+ df = pl.read_parquet(self.file, **self._get_loading_kwargs())
305
+ metadata = utils.get_file_and_dataframe_metadata(self.file, df)
306
+ return df, metadata
307
+
308
+ @classmethod
309
+ def name(cls) -> str:
310
+ return "parquet"
311
+
312
+
313
+ @dataclasses.dataclass
314
+ class PolarsParquetWriter(DataSaver):
315
+ """Class specifically to handle saving CSV files with Polars.
316
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_parquet.html
317
+ """
318
+
319
+ file: BytesIO | TextIOWrapper | str | Path
320
+ # kwargs:
321
+ compression: Any = "zstd"
322
+ compression_level: int = None
323
+ statistics: bool = False
324
+ row_group_size: int = None
325
+ use_pyarrow: bool = False
326
+ pyarrow_options: dict[str, Any] = None
327
+
328
+ @classmethod
329
+ def applicable_types(cls) -> Collection[type]:
330
+ return [DATAFRAME_TYPE, pl.LazyFrame]
331
+
332
+ def _get_saving_kwargs(self):
333
+ kwargs = {}
334
+ if self.compression is not None:
335
+ kwargs["compression"] = self.compression
336
+ if self.compression is not None:
337
+ kwargs["compression_level"] = self.compression_level
338
+ if self.compression is not None:
339
+ kwargs["statistics"] = self.statistics
340
+ if self.compression is not None:
341
+ kwargs["row_group_size"] = self.row_group_size
342
+ if self.compression is not None:
343
+ kwargs["use_pyarrow"] = self.use_pyarrow
344
+ if self.compression is not None:
345
+ kwargs["pyarrow_options"] = self.pyarrow_options
346
+ return kwargs
347
+
348
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
349
+ if isinstance(data, pl.LazyFrame):
350
+ data = data.collect()
351
+
352
+ data.write_parquet(self.file, **self._get_saving_kwargs())
353
+
354
+ return utils.get_file_and_dataframe_metadata(self.file, data)
355
+
356
+ @classmethod
357
+ def name(cls) -> str:
358
+ return "parquet"
359
+
360
+
361
+ @dataclasses.dataclass
362
+ class PolarsFeatherReader(DataLoader):
363
+ """
364
+ Class specifically to handle loading Feather/Arrow IPC files with Polars.
365
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_ipc.html
366
+ """
367
+
368
+ source: str | BinaryIO | BytesIO | Path | bytes
369
+ # kwargs:
370
+ columns: list[str] | list[int] | None = None
371
+ n_rows: int | None = None
372
+ use_pyarrow: bool = False
373
+ memory_map: bool = True
374
+ storage_options: dict[str, Any] | None = None
375
+ row_count_name: str | None = None
376
+ row_count_offset: int = 0
377
+ rechunk: bool = True
378
+
379
+ @classmethod
380
+ def applicable_types(cls) -> Collection[type]:
381
+ return [DATAFRAME_TYPE]
382
+
383
+ def _get_loading_kwargs(self):
384
+ kwargs = {}
385
+ if self.columns is not None:
386
+ kwargs["columns"] = self.columns
387
+ if self.n_rows is not None:
388
+ kwargs["n_rows"] = self.n_rows
389
+ if self.use_pyarrow is not None:
390
+ kwargs["use_pyarrow"] = self.use_pyarrow
391
+ if self.memory_map is not None:
392
+ kwargs["memory_map"] = self.memory_map
393
+ if self.storage_options is not None:
394
+ kwargs["storage_options"] = self.storage_options
395
+ if self.row_count_name is not None:
396
+ kwargs["row_count_name"] = self.row_count_name
397
+ if self.row_count_offset is not None:
398
+ kwargs["row_count_offset"] = self.row_count_offset
399
+ if self.rechunk is not None:
400
+ kwargs["rechunk"] = self.rechunk
401
+ return kwargs
402
+
403
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
404
+ df = pl.read_ipc(self.source, **self._get_loading_kwargs())
405
+ metadata = utils.get_file_metadata(self.source)
406
+ return df, metadata
407
+
408
+ @classmethod
409
+ def name(cls) -> str:
410
+ return "feather"
411
+
412
+
413
+ @dataclasses.dataclass
414
+ class PolarsFeatherWriter(DataSaver):
415
+ """
416
+ Class specifically to handle saving Feather/Arrow IPC files with Polars.
417
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_ipc.html
418
+ """
419
+
420
+ file: BinaryIO | BytesIO | str | Path | None = None
421
+ # kwargs:
422
+ compression: IpcCompression = "uncompressed"
423
+
424
+ @classmethod
425
+ def applicable_types(cls) -> Collection[type]:
426
+ return [DATAFRAME_TYPE, pl.LazyFrame]
427
+
428
+ def _get_saving_kwargs(self):
429
+ kwargs = {}
430
+ if self.compression is not None:
431
+ kwargs["compression"] = self.compression
432
+ return kwargs
433
+
434
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
435
+ if isinstance(data, pl.LazyFrame):
436
+ data = data.collect()
437
+ data.write_ipc(self.file, **self._get_saving_kwargs())
438
+ return utils.get_file_and_dataframe_metadata(self.file, data)
439
+
440
+ @classmethod
441
+ def name(cls) -> str:
442
+ return "feather"
443
+
444
+
445
+ @dataclasses.dataclass
446
+ class PolarsAvroReader(DataLoader):
447
+ """Class specifically to handle loading Avro files with polars
448
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_avro.html
449
+ """
450
+
451
+ file: str | TextIO | BytesIO | Path | BinaryIO | bytes
452
+ # kwargs:
453
+ columns: list[int] | list[str] | None = None
454
+ n_rows: int | None = None
455
+
456
+ @classmethod
457
+ def applicable_types(cls) -> Collection[type]:
458
+ return [DATAFRAME_TYPE]
459
+
460
+ def _get_loading_kwargs(self):
461
+ kwargs = {}
462
+ if self.columns is not None:
463
+ kwargs["columns"] = self.columns
464
+ if self.n_rows is not None:
465
+ kwargs["n_rows"] = self.n_rows
466
+ return kwargs
467
+
468
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
469
+ df = pl.read_avro(self.file, **self._get_loading_kwargs())
470
+ metadata = utils.get_file_and_dataframe_metadata(self.file, df)
471
+ return df, metadata
472
+
473
+ @classmethod
474
+ def name(cls) -> str:
475
+ return "avro"
476
+
477
+
478
+ @dataclasses.dataclass
479
+ class PolarsAvroWriter(DataSaver):
480
+ """Class specifically to handle saving Avro files with Polars.
481
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_avro.html
482
+ """
483
+
484
+ file: BytesIO | TextIOWrapper | str | Path
485
+ # kwargs:
486
+ compression: Any = "uncompressed"
487
+
488
+ @classmethod
489
+ def applicable_types(cls) -> Collection[type]:
490
+ return [DATAFRAME_TYPE, pl.LazyFrame]
491
+
492
+ def _get_saving_kwargs(self):
493
+ kwargs = {}
494
+ if self.compression is not None:
495
+ kwargs["compression"] = self.compression
496
+ return kwargs
497
+
498
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
499
+ if isinstance(data, pl.LazyFrame):
500
+ data = data.collect()
501
+
502
+ data.write_avro(self.file, **self._get_saving_kwargs())
503
+ return utils.get_file_and_dataframe_metadata(self.file, data)
504
+
505
+ @classmethod
506
+ def name(cls) -> str:
507
+ return "avro"
508
+
509
+
510
+ @dataclasses.dataclass
511
+ class PolarsJSONReader(DataLoader):
512
+ """
513
+ Class specifically to handle loading JSON files with Polars.
514
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_json.html
515
+ """
516
+
517
+ source: str | Path | IOBase | bytes
518
+ schema: SchemaDefinition = None
519
+ schema_overrides: SchemaDefinition = None
520
+
521
+ @classmethod
522
+ def applicable_types(cls) -> Collection[type]:
523
+ return [DATAFRAME_TYPE]
524
+
525
+ def _get_loading_kwargs(self):
526
+ kwargs = {}
527
+ if self.schema is not None:
528
+ kwargs["schema"] = self.schema
529
+ if self.schema_overrides is not None:
530
+ kwargs["schema_overrides"] = self.schema_overrides
531
+ return kwargs
532
+
533
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
534
+ df = pl.read_json(self.source, **self._get_loading_kwargs())
535
+ metadata = utils.get_file_metadata(self.source)
536
+ return df, metadata
537
+
538
+ @classmethod
539
+ def name(cls) -> str:
540
+ return "json"
541
+
542
+
543
+ @dataclasses.dataclass
544
+ class PolarsJSONWriter(DataSaver):
545
+ """
546
+ Class specifically to handle saving JSON files with Polars.
547
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_json.html
548
+ """
549
+
550
+ file: IOBase | str | Path
551
+ pretty: bool = False
552
+ row_oriented: bool = False
553
+
554
+ @classmethod
555
+ def applicable_types(cls) -> Collection[type]:
556
+ return [DATAFRAME_TYPE, pl.LazyFrame]
557
+
558
+ def _get_saving_kwargs(self):
559
+ kwargs = {}
560
+ if self.pretty is not None:
561
+ kwargs["pretty"] = self.pretty
562
+ if self.row_oriented is not None:
563
+ kwargs["row_oriented"] = self.row_oriented
564
+ return kwargs
565
+
566
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
567
+ if isinstance(data, pl.LazyFrame):
568
+ data = data.collect()
569
+
570
+ data.write_json(self.file, **self._get_saving_kwargs())
571
+ return utils.get_file_and_dataframe_metadata(self.file, data)
572
+
573
+ @classmethod
574
+ def name(cls) -> str:
575
+ return "json"
576
+
577
+
578
+ @dataclasses.dataclass
579
+ class PolarsSpreadsheetReader(DataLoader):
580
+ """
581
+ Class specifically to handle loading Spreadsheet files with Polars.
582
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_excel.html
583
+ """
584
+
585
+ source: str | Path | IOBase | bytes
586
+ # kwargs:
587
+ sheet_id: int | Sequence[int] | None = None
588
+ sheet_name: str | list[str] | tuple[str] | None = None
589
+ engine: Literal["xlsx2csv", "openpyxl", "pyxlsb", "odf", "xlrd", "xlsxwriter"] = "xlsx2csv"
590
+ engine_options: dict[str, Any] | None = None
591
+ read_options: dict[str, Any] | None = None
592
+ schema_overrides: dict[str, Any] | None = None
593
+ raise_if_empty: bool = True
594
+
595
+ @classmethod
596
+ def applicable_types(cls) -> Collection[type]:
597
+ return [DATAFRAME_TYPE]
598
+
599
+ def _get_loading_kwargs(self):
600
+ kwargs = {}
601
+ if self.sheet_id is not None:
602
+ kwargs["sheet_id"] = self.sheet_id
603
+ if self.sheet_name is not None:
604
+ kwargs["sheet_name"] = self.sheet_name
605
+ if self.engine is not None:
606
+ kwargs["engine"] = self.engine
607
+ if self.engine_options is not None:
608
+ kwargs["engine_options"] = self.engine_options
609
+ if self.read_options is not None:
610
+ kwargs["read_options"] = self.read_options
611
+ if self.schema_overrides is not None:
612
+ kwargs["schema_overrides"] = self.schema_overrides
613
+ if self.raise_if_empty is not None:
614
+ kwargs["raise_if_empty"] = self.raise_if_empty
615
+ return kwargs
616
+
617
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
618
+ df = pl.read_excel(self.source, **self._get_loading_kwargs())
619
+ metadata = utils.get_file_metadata(self.source)
620
+ return df, metadata
621
+
622
+ @classmethod
623
+ def name(cls) -> str:
624
+ return "spreadsheet"
625
+
626
+
627
+ @dataclasses.dataclass
628
+ class PolarsSpreadsheetWriter(DataSaver):
629
+ """
630
+ Class specifically to handle saving Spreadsheet files with Polars.
631
+ Should map to https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_excel.html
632
+ """
633
+
634
+ # importing here because this is where it's used. Can move later.
635
+ # but yeah the polars type aliases weren't resolving well in python 3.9
636
+ # so stripped/reduced them appropriately.
637
+ from polars.datatypes import DataType, DataTypeClass
638
+ from polars.type_aliases import ColumnTotalsDefinition, RowTotalsDefinition
639
+
640
+ workbook: Workbook | BytesIO | Path | str
641
+ worksheet: str | None = None
642
+ # kwargs:
643
+ position: tuple[int, int] | str = "A1"
644
+ table_style: str | dict[str, Any] | None = None
645
+ table_name: str | None = None
646
+ column_formats: Mapping[str | tuple[str, ...], str | Mapping[str, str]] | None = None
647
+ dtype_formats: dict[DataType | DataTypeClass, str] | None = None
648
+ conditional_formats: (
649
+ Mapping[str | Collection[str], str | Mapping[str, Any] | Sequence[str | Mapping[str, Any]]]
650
+ | None
651
+ ) = None
652
+ header_format: dict[str, Any] | None = None
653
+ column_totals: ColumnTotalsDefinition | None = None
654
+ column_widths: Mapping[str, tuple[str, ...] | int] | int | None = None
655
+ row_totals: RowTotalsDefinition | None = None
656
+ row_heights: dict[int | tuple[int, ...], int] | int | None = None
657
+ sparklines: dict[str, Sequence[str] | dict[str, Any]] | None = None
658
+ formulas: dict[str, str | dict[str, str]] | None = None
659
+ float_precision: int = 3
660
+ include_header: bool = True
661
+ autofilter: bool = True
662
+ autofit: bool = False
663
+ hidden_columns: Sequence[str] | str | None = None
664
+ hide_gridlines: bool = None
665
+ sheet_zoom: int | None = None
666
+ freeze_panes: (
667
+ str | tuple[int, int] | tuple[str, int, int] | tuple[int, int, int, int] | None
668
+ ) = None
669
+
670
+ @classmethod
671
+ def applicable_types(cls) -> Collection[type]:
672
+ return [DATAFRAME_TYPE, pl.LazyFrame]
673
+
674
+ def _get_saving_kwargs(self):
675
+ kwargs = {}
676
+ if self.position is not None:
677
+ kwargs["position"] = self.position
678
+ if self.table_style is not None:
679
+ kwargs["table_style"] = self.table_style
680
+ if self.table_name is not None:
681
+ kwargs["table_name"] = self.table_name
682
+ if self.column_formats is not None:
683
+ kwargs["column_formats"] = self.column_formats
684
+ if self.dtype_formats is not None:
685
+ kwargs["dtype_formats"] = self.dtype_formats
686
+ if self.conditional_formats is not None:
687
+ kwargs["conditional_formats"] = self.conditional_formats
688
+ if self.header_format is not None:
689
+ kwargs["header_format"] = self.header_format
690
+ if self.column_totals is not None:
691
+ kwargs["column_totals"] = self.column_totals
692
+ if self.column_widths is not None:
693
+ kwargs["column_widths"] = self.column_widths
694
+ if self.row_totals is not None:
695
+ kwargs["row_totals"] = self.row_totals
696
+ if self.row_heights is not None:
697
+ kwargs["row_heights"] = self.row_heights
698
+ if self.sparklines is not None:
699
+ kwargs["sparklines"] = self.sparklines
700
+ if self.formulas is not None:
701
+ kwargs["formulas"] = self.formulas
702
+ if self.float_precision is not None:
703
+ kwargs["float_precision"] = self.float_precision
704
+ if self.include_header is not None:
705
+ kwargs["include_header"] = self.include_header
706
+ if self.autofilter is not None:
707
+ kwargs["autofilter"] = self.autofilter
708
+ if self.autofit is not None:
709
+ kwargs["autofit"] = self.autofit
710
+ if self.hidden_columns is not None:
711
+ kwargs["hidden_columns"] = self.hidden_columns
712
+ if self.hide_gridlines is not None:
713
+ kwargs["hide_gridlines"] = self.hide_gridlines
714
+ if self.sheet_zoom is not None:
715
+ kwargs["sheet_zoom"] = self.sheet_zoom
716
+ if self.freeze_panes is not None:
717
+ kwargs["freeze_panes"] = self.freeze_panes
718
+ return kwargs
719
+
720
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
721
+ if isinstance(data, pl.LazyFrame):
722
+ data = data.collect()
723
+
724
+ data.write_excel(self.workbook, self.worksheet, **self._get_saving_kwargs())
725
+ return utils.get_file_and_dataframe_metadata(self.workbook, data)
726
+
727
+ @classmethod
728
+ def name(cls) -> str:
729
+ return "spreadsheet"
730
+
731
+
732
+ @dataclasses.dataclass
733
+ class PolarsDatabaseReader(DataLoader):
734
+ """
735
+ Class specifically to handle loading DataFrame from a database.
736
+ """
737
+
738
+ query: str
739
+ connection: str
740
+ # kwargs:
741
+ iter_batches: bool = False
742
+ batch_size: int | None = None
743
+ schema_overrides: dict[str, Any] | None = None
744
+ infer_schema_length: int | None = None
745
+ execute_options: dict[str, Any] | None = None
746
+
747
+ @classmethod
748
+ def applicable_types(cls) -> Collection[type]:
749
+ return [DATAFRAME_TYPE]
750
+
751
+ def _get_loading_kwargs(self):
752
+ kwargs = {}
753
+ if self.iter_batches is not None:
754
+ kwargs["iter_batches"] = self.iter_batches
755
+ if self.batch_size is not None:
756
+ kwargs["batch_size"] = self.batch_size
757
+ if self.schema_overrides is not None:
758
+ kwargs["schema_overrides"] = self.schema_overrides
759
+ if self.infer_schema_length is not None:
760
+ kwargs["infer_schema_length"] = self.infer_schema_length
761
+ if self.execute_options is not None:
762
+ kwargs["execute_options"] = self.execute_options
763
+ return kwargs
764
+
765
+ def load_data(self, type_: type) -> tuple[DATAFRAME_TYPE, dict[str, Any]]:
766
+ df = pl.read_database(
767
+ query=self.query,
768
+ connection=self.connection,
769
+ **self._get_loading_kwargs(),
770
+ )
771
+ metadata = utils.get_file_and_dataframe_metadata(self.query, df)
772
+ return df, metadata
773
+
774
+ @classmethod
775
+ def name(cls) -> str:
776
+ return "database"
777
+
778
+
779
+ @dataclasses.dataclass
780
+ class PolarsDatabaseWriter(DataSaver):
781
+ """
782
+ Class specifically to handle saving DataFrame to a database.
783
+ """
784
+
785
+ table_name: str
786
+ connection: str
787
+ if_table_exists: Literal["fail", "replace", "append"] = "fail"
788
+ engine: Literal["auto", "sqlalchemy", "adbc"] = "sqlalchemy"
789
+
790
+ @classmethod
791
+ def applicable_types(cls) -> Collection[type]:
792
+ return [DATAFRAME_TYPE, pl.LazyFrame]
793
+
794
+ def _get_saving_kwargs(self):
795
+ kwargs = {}
796
+ if self.if_table_exists is not None:
797
+ kwargs["if_table_exists"] = self.if_table_exists
798
+ if self.engine is not None:
799
+ kwargs["engine"] = self.engine
800
+ return kwargs
801
+
802
+ def save_data(self, data: DATAFRAME_TYPE | pl.LazyFrame) -> dict[str, Any]:
803
+ if isinstance(data, pl.LazyFrame):
804
+ data = data.collect()
805
+
806
+ data.write_database(
807
+ table_name=self.table_name,
808
+ connection=self.connection,
809
+ **self._get_saving_kwargs(),
810
+ )
811
+ return utils.get_file_and_dataframe_metadata(self.table_name, data)
812
+
813
+ @classmethod
814
+ def name(cls) -> str:
815
+ return "database"
816
+
817
+
818
+ def register_data_loaders():
819
+ """Function to register the data loaders for this extension."""
820
+ for loader in [
821
+ PolarsCSVReader,
822
+ PolarsCSVWriter,
823
+ PolarsParquetReader,
824
+ PolarsParquetWriter,
825
+ PolarsFeatherReader,
826
+ PolarsFeatherWriter,
827
+ PolarsAvroReader,
828
+ PolarsAvroWriter,
829
+ PolarsJSONReader,
830
+ PolarsJSONWriter,
831
+ PolarsDatabaseReader,
832
+ PolarsDatabaseWriter,
833
+ PolarsSpreadsheetReader,
834
+ PolarsSpreadsheetWriter,
835
+ ]:
836
+ registry.register_adapter(loader)