datakraken 1.7.2__tar.gz → 1.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {datakraken-1.7.2 → datakraken-1.7.4}/PKG-INFO +1 -1
- {datakraken-1.7.2 → datakraken-1.7.4}/pyproject.toml +1 -1
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/analysis/data_manipulation.py +8 -4
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/connector.py +38 -9
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/result_export.py +7 -1
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/result_export_extensions.py +16 -1
- datakraken-1.7.4/src/kraken/importing/data_import.py +162 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/ribosome.py +2 -2
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/sql_execution.py +4 -4
- datakraken-1.7.2/src/kraken/importing/data_import.py +0 -125
- {datakraken-1.7.2 → datakraken-1.7.4}/.gitignore +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/LICENSE +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/README.md +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/analysis/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/data_types.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/pack_lists.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/packs.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/support.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/credential_manager.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/credentials.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/helpers.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/integrity.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/save_connection.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/save_convenience.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/demo/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/demo/generate_demo_data.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exceptions.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_graphs.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_support.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_support_lists.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_theme.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/models.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/importing/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/parsing.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/scanning.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/support_classes.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/config.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/integrity.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/oracle/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/py.typed +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/sql_extraction.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/support.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/progress.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/readout.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support_checks.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support_classes.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/warnings.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/__init__.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/data_upload.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/support.py +0 -0
- {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/uploader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datakraken
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
4
4
|
Summary: Kraken is a convenience package that orchestrates data extraction by integrating SQL with Python, while managing safe storage and recall of sensitive connection credentials.
|
|
5
5
|
Project-URL: Repository, https://github.com/SETT-Centre-Data-and-AI/Kraken
|
|
6
6
|
Author: Cai Davis, Michael George, Faizan Hemotra
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "datakraken"
|
|
3
|
-
version = "1.7.
|
|
3
|
+
version = "1.7.4"
|
|
4
4
|
description = "Kraken is a convenience package that orchestrates data extraction by integrating SQL with Python, while managing safe storage and recall of sensitive connection credentials."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Cai Davis" },
|
|
@@ -4,7 +4,7 @@ import numpy as np
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from IPython.display import display
|
|
6
6
|
from pandas import DataFrame
|
|
7
|
-
from pandas.api.types import is_numeric_dtype
|
|
7
|
+
from pandas.api.types import is_numeric_dtype, is_object_dtype, is_string_dtype
|
|
8
8
|
|
|
9
9
|
from kraken.classes.data_types import StatsPack
|
|
10
10
|
from kraken.exceptions import DuplicateColumnError
|
|
@@ -100,7 +100,7 @@ def examine(
|
|
|
100
100
|
if is_numeric_dtype(df[column])
|
|
101
101
|
else False
|
|
102
102
|
)
|
|
103
|
-
if df[column]
|
|
103
|
+
if is_object_dtype(df[column]) or is_string_dtype(df[column]):
|
|
104
104
|
max_length = df[column].dropna().astype(str).str.len().max()
|
|
105
105
|
max_length = int(max_length) if pd.notna(max_length) else None
|
|
106
106
|
else:
|
|
@@ -205,7 +205,7 @@ def examine(
|
|
|
205
205
|
def date_converter_df(
|
|
206
206
|
df: DataFrame, columns: str | list | None = None, readouts: bool = True
|
|
207
207
|
) -> DataFrame:
|
|
208
|
-
"""Checks
|
|
208
|
+
"""Checks object/string columns for missed dates and converts those found in place.
|
|
209
209
|
|
|
210
210
|
Args:
|
|
211
211
|
df (DataFrame): DataFrame
|
|
@@ -233,7 +233,11 @@ def date_converter_df(
|
|
|
233
233
|
columns = [columns]
|
|
234
234
|
|
|
235
235
|
for col in columns:
|
|
236
|
-
if
|
|
236
|
+
if (
|
|
237
|
+
col is None
|
|
238
|
+
or isinstance(df[col], DataFrame)
|
|
239
|
+
or not (is_object_dtype(df[col]) or is_string_dtype(df[col]))
|
|
240
|
+
):
|
|
237
241
|
pass
|
|
238
242
|
else:
|
|
239
243
|
values = df[col].map(
|
|
@@ -3,17 +3,17 @@ from abc import ABC, abstractmethod
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from typing import Any, Generic, Literal, Protocol, Type, TypeVar, runtime_checkable
|
|
5
5
|
|
|
6
|
+
import pandas as pd
|
|
6
7
|
import pyodbc # type: ignore[import-not-found]
|
|
7
8
|
import sqlalchemy as sa
|
|
8
9
|
from pandas import DataFrame
|
|
9
10
|
from sqlalchemy.engine.url import URL
|
|
10
11
|
|
|
11
|
-
from kraken.analysis.data_manipulation import check_df_integers as clean_dataframe
|
|
12
12
|
from kraken.connection.support import (
|
|
13
|
+
DEFAULT_ARRAYSIZE,
|
|
14
|
+
_check_execution_error,
|
|
13
15
|
_compile_pyodbc_connection_string,
|
|
14
16
|
_split_pyodbc_connection_string,
|
|
15
|
-
_check_execution_error,
|
|
16
|
-
DEFAULT_ARRAYSIZE,
|
|
17
17
|
)
|
|
18
18
|
from kraken.credentials.credentials import Credentials
|
|
19
19
|
from kraken.credentials.helpers import fetch_credentials
|
|
@@ -266,8 +266,9 @@ class Connector(ABC, Generic[TConn, TCursor]):
|
|
|
266
266
|
- close (bool): If True, closes the connection after executing. Defaults to None, using the Connector default (Connector.autoclose).
|
|
267
267
|
- batch_size (int): Downloads data in batches. Uses current Connector.batch_size if None. Set to 0 to
|
|
268
268
|
download all data without batching.
|
|
269
|
-
- clean_df (bool):
|
|
270
|
-
|
|
269
|
+
- clean_df (bool): Preserves fetched scalar types during DataFrame construction,
|
|
270
|
+
using nullable integer/boolean dtypes where needed without a float intermediate.
|
|
271
|
+
Float values remain floats. False uses ordinary pandas inference.
|
|
271
272
|
- check_column_duplicates (bool): Checks DataFrame for duplicate column names and throws warning.
|
|
272
273
|
- header_indent (str): Prefix each readout with an indent (e.g. ' -> ')
|
|
273
274
|
- progress (bool): If True, activates query progress bar. Switching to False may save a little
|
|
@@ -432,7 +433,32 @@ class Connector(ABC, Generic[TConn, TCursor]):
|
|
|
432
433
|
if df is None:
|
|
433
434
|
return
|
|
434
435
|
|
|
435
|
-
|
|
436
|
+
if clean_df:
|
|
437
|
+
for position in range(len(df.columns)):
|
|
438
|
+
column = df.iloc[:, position]
|
|
439
|
+
raw_values = column.to_numpy()
|
|
440
|
+
present = pd.notna(raw_values)
|
|
441
|
+
nullable = not present.all()
|
|
442
|
+
values = raw_values[present] if nullable else raw_values
|
|
443
|
+
scalar_type = pd.api.types.infer_dtype(values, skipna=True)
|
|
444
|
+
converted: pd.Series[Any]
|
|
445
|
+
if scalar_type == "integer":
|
|
446
|
+
dtype: Literal["Int64", "int64", "UInt64", "uint64"]
|
|
447
|
+
minimum, maximum = values.min(), values.max()
|
|
448
|
+
if -(2**63) <= minimum and maximum < 2**63:
|
|
449
|
+
dtype = "Int64" if nullable else "int64"
|
|
450
|
+
elif 0 <= minimum and maximum < 2**64:
|
|
451
|
+
dtype = "UInt64" if nullable else "uint64"
|
|
452
|
+
else:
|
|
453
|
+
continue
|
|
454
|
+
converted = column.astype(dtype)
|
|
455
|
+
elif scalar_type == "boolean":
|
|
456
|
+
converted = column.astype("boolean" if nullable else "bool")
|
|
457
|
+
elif scalar_type in ("mixed-integer", "mixed-integer-float"):
|
|
458
|
+
continue
|
|
459
|
+
else:
|
|
460
|
+
converted = column.infer_objects()
|
|
461
|
+
df.isetitem(position, converted.array)
|
|
436
462
|
if check_column_duplicates and df.columns.has_duplicates:
|
|
437
463
|
dupe_cols = df.columns[df.columns.duplicated()].to_list()
|
|
438
464
|
readout.warn(
|
|
@@ -495,7 +521,9 @@ class Connector(ABC, Generic[TConn, TCursor]):
|
|
|
495
521
|
progress.update_header(
|
|
496
522
|
header=f"{indent}Query: {query_name} Processing |"
|
|
497
523
|
)
|
|
498
|
-
df = DataFrame(
|
|
524
|
+
df = DataFrame(
|
|
525
|
+
data=rows, columns=columns, dtype=object if clean_df else None
|
|
526
|
+
)
|
|
499
527
|
df = self._process_dataframe(
|
|
500
528
|
df=df,
|
|
501
529
|
clean_df=clean_df,
|
|
@@ -626,8 +654,9 @@ class SaConnector(
|
|
|
626
654
|
close (bool): If True, closes the connection after executing. Defaults to None, using the Connector default (Connector.autoclose).
|
|
627
655
|
batch_size (int): Downloads data in batches. Uses current Connector.batch_size if None. Set to 0 to
|
|
628
656
|
download all data without batching.
|
|
629
|
-
clean_df (bool):
|
|
630
|
-
|
|
657
|
+
clean_df (bool): Preserves fetched scalar types during DataFrame construction,
|
|
658
|
+
using nullable integer/boolean dtypes where needed without a float intermediate.
|
|
659
|
+
Float values remain floats. False uses ordinary pandas inference.
|
|
631
660
|
check_column_duplicates (bool): Checks DataFrame for duplicate column names and throws warning.
|
|
632
661
|
header_indent (str): Prefix each readout with an indent (e.g. ' -> ')
|
|
633
662
|
progress (bool): If True, activates query progress bar. Switching to False may save a little
|
|
@@ -42,10 +42,16 @@ def export_results(
|
|
|
42
42
|
**kwargs: Any,
|
|
43
43
|
) -> None:
|
|
44
44
|
"""
|
|
45
|
-
Exports results or a dataframe to
|
|
45
|
+
Exports results or a dataframe to CSV, XLSX or Parquet without its index.
|
|
46
46
|
For filetypes supporting multiple dataframes (such as xlsx), the filename argument can be provided.
|
|
47
47
|
For filetypes supporting single dataframes only (such as csv) the filename provided will be appended to the prefix (if given), with the dataframe name used instead.
|
|
48
48
|
|
|
49
|
+
CSV keeps datetime time components, including midnight, but carries no schema.
|
|
50
|
+
XLSX writes string values literally and uses distinct date/datetime display formats;
|
|
51
|
+
Excel numeric precision and timestamp/timezone limits still apply. Parquet is the
|
|
52
|
+
typed round-trip format for supported column types. Export does not clean or mutate
|
|
53
|
+
caller dataframes. Promote meaningful indexes to columns before exporting.
|
|
54
|
+
|
|
49
55
|
Args:
|
|
50
56
|
results (Result | ResultList | DataFrame): results to export.
|
|
51
57
|
directory (str | Path): Output directory. Created if not found.
|
|
@@ -14,6 +14,9 @@ def _prepare_results_xlsx(export_pack: dict[str, pd.DataFrame]) -> bytes:
|
|
|
14
14
|
engine="xlsxwriter",
|
|
15
15
|
date_format="DD/MM/YYYY",
|
|
16
16
|
datetime_format="DD/MM/YYYY HH:MM:SS",
|
|
17
|
+
engine_kwargs={
|
|
18
|
+
"options": {"strings_to_formulas": False, "strings_to_urls": False}
|
|
19
|
+
},
|
|
17
20
|
) as writer:
|
|
18
21
|
for df_name, dataframe in export_pack.items():
|
|
19
22
|
readout.print(f"\n - writing sheet '{df_name}'...", end="")
|
|
@@ -28,7 +31,19 @@ def _prepare_results_csv(
|
|
|
28
31
|
) -> bytes:
|
|
29
32
|
buffer = BytesIO()
|
|
30
33
|
for dataframe in export_pack.values():
|
|
31
|
-
dataframe.
|
|
34
|
+
csv_frame = dataframe.copy(deep=False)
|
|
35
|
+
for position in range(len(dataframe.columns)):
|
|
36
|
+
column = dataframe.iloc[:, position]
|
|
37
|
+
if pd.api.types.is_datetime64_any_dtype(column.dtype) and not isinstance(
|
|
38
|
+
column.dtype, pd.DatetimeTZDtype
|
|
39
|
+
):
|
|
40
|
+
csv_frame.isetitem(
|
|
41
|
+
position,
|
|
42
|
+
column.map(
|
|
43
|
+
lambda value: "" if pd.isna(value) else value.isoformat(sep=" ")
|
|
44
|
+
).array,
|
|
45
|
+
)
|
|
46
|
+
csv_frame.to_csv(buffer, sep=delimiter, index=False)
|
|
32
47
|
return buffer.getvalue()
|
|
33
48
|
|
|
34
49
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from kraken.classes.pack_lists import ResultList
|
|
9
|
+
from kraken.classes.packs import Result
|
|
10
|
+
from kraken.support.readout import readout
|
|
11
|
+
from kraken.support.support import _check_filetype, _load_filepaths, calculate_runtime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_spreadsheets(
|
|
15
|
+
filepaths: str | list[str] | Path | list[Path], clean_df: bool = True, **kwargs: Any
|
|
16
|
+
) -> ResultList:
|
|
17
|
+
"""Imports CSV, XLS, XLSX and Parquet files into a ResultList. Outputs results in
|
|
18
|
+
order of user directory/file input. Raises errors if a filepath does not point to
|
|
19
|
+
a supported file or a valid directory, or if no supported files are detected. Raises warnings
|
|
20
|
+
if any given directory filepath returns no files.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
filepaths (str | list[str]): Path or list of paths to spreadsheet files or
|
|
24
|
+
directories of spreadsheet files
|
|
25
|
+
clean_df (bool): Uses pandas' numpy_nullable reader backend by default for
|
|
26
|
+
CSV/Excel, avoiding float intermediates for nullable signed integers.
|
|
27
|
+
False leaves backend selection to pandas. Explicit dtype/backend options
|
|
28
|
+
are honored. Parquet retains its stored schema regardless of this flag.
|
|
29
|
+
kwargs: Forwarded to the relevant pandas reader. CSV/Excel default to
|
|
30
|
+
keep_default_na=False and na_values=[""], preserving literal NA strings
|
|
31
|
+
but treating blanks as missing. Override these defaults as needed.
|
|
32
|
+
Use dtype for identifiers or unsigned integers, converters for exact
|
|
33
|
+
decimals, and parse_dates for CSV timestamps. These formats do not
|
|
34
|
+
retain a full column schema.
|
|
35
|
+
Options must be valid for every requested format when mixing file types.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
ResultList: Imported dataframes with filename, dataframe name and path metadata.
|
|
39
|
+
"""
|
|
40
|
+
start = datetime.now()
|
|
41
|
+
supported_extensions = ["csv", "xlsx", "xls", "parquet"]
|
|
42
|
+
reader_kwargs = {"keep_default_na": False, "na_values": [""], **kwargs}
|
|
43
|
+
if clean_df:
|
|
44
|
+
reader_kwargs.setdefault("dtype_backend", "numpy_nullable")
|
|
45
|
+
parse_dates = reader_kwargs.get("parse_dates")
|
|
46
|
+
declared_dtypes = reader_kwargs.get("dtype")
|
|
47
|
+
inferred_date_columns: list[str | int] = []
|
|
48
|
+
if "dtype_backend" in reader_kwargs and isinstance(parse_dates, list):
|
|
49
|
+
date_dtypes: dict[str | int, Any]
|
|
50
|
+
if declared_dtypes is None:
|
|
51
|
+
date_dtypes = {}
|
|
52
|
+
elif isinstance(declared_dtypes, dict):
|
|
53
|
+
date_dtypes = declared_dtypes.copy()
|
|
54
|
+
else:
|
|
55
|
+
date_dtypes = defaultdict(lambda: declared_dtypes)
|
|
56
|
+
for column in parse_dates:
|
|
57
|
+
if isinstance(column, (str, int)) and column not in date_dtypes:
|
|
58
|
+
date_dtypes[column] = object
|
|
59
|
+
inferred_date_columns.append(column)
|
|
60
|
+
reader_kwargs["dtype"] = date_dtypes
|
|
61
|
+
|
|
62
|
+
# Load Filepaths
|
|
63
|
+
filepaths = _load_filepaths(filepaths, supported_extensions)
|
|
64
|
+
spreadsheet_packs = ResultList()
|
|
65
|
+
df_name_list = []
|
|
66
|
+
df_name_duplicates = set()
|
|
67
|
+
df_name_duplicate_count = 0
|
|
68
|
+
|
|
69
|
+
readout.print("Loading dataframes from spreadsheets...")
|
|
70
|
+
# Load spreadsheets
|
|
71
|
+
for filepath in filepaths:
|
|
72
|
+
# Load csv dataframes
|
|
73
|
+
if _check_filetype(filepath, "csv"):
|
|
74
|
+
filename = Path(filepath).name
|
|
75
|
+
df_name = Path(filepath).stem
|
|
76
|
+
readout.print(f" - From csv '{filename}'...", end="")
|
|
77
|
+
df = pd.read_csv(filepath, **reader_kwargs)
|
|
78
|
+
_restore_parsed_dates(df, inferred_date_columns)
|
|
79
|
+
spreadsheet_packs.append(
|
|
80
|
+
Result(
|
|
81
|
+
filename=filename,
|
|
82
|
+
df_name=df_name,
|
|
83
|
+
df=df,
|
|
84
|
+
filepath=filepath,
|
|
85
|
+
db_alias="",
|
|
86
|
+
platform="",
|
|
87
|
+
sql="",
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
df_name_list.append(df_name)
|
|
91
|
+
readout.print(f" loaded dataframe '{df_name}'")
|
|
92
|
+
|
|
93
|
+
# Load xlsx dataframes
|
|
94
|
+
if _check_filetype(filepath, ["xlsx", "xls"]):
|
|
95
|
+
filename = Path(filepath).name
|
|
96
|
+
readout.print(f" - From xlsx '{filename}'...")
|
|
97
|
+
with pd.ExcelFile(filepath) as file:
|
|
98
|
+
for sheet_name in file.sheet_names:
|
|
99
|
+
df_name = str(sheet_name)
|
|
100
|
+
df = file.parse(df_name, **reader_kwargs)
|
|
101
|
+
_restore_parsed_dates(df, inferred_date_columns)
|
|
102
|
+
spreadsheet_packs.append(
|
|
103
|
+
Result(
|
|
104
|
+
filename=filename,
|
|
105
|
+
df_name=df_name,
|
|
106
|
+
df=df,
|
|
107
|
+
filepath=filepath,
|
|
108
|
+
db_alias="",
|
|
109
|
+
platform="",
|
|
110
|
+
sql="",
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
df_name_list.append(df_name)
|
|
114
|
+
readout.print(f" - loaded dataframe '{df_name}'")
|
|
115
|
+
|
|
116
|
+
if _check_filetype(filepath, "parquet"):
|
|
117
|
+
filename = Path(filepath).name
|
|
118
|
+
df_name = Path(filepath).stem
|
|
119
|
+
readout.print(f" - From parquet '{filename}'...", end="")
|
|
120
|
+
df = pd.read_parquet(filepath, **kwargs)
|
|
121
|
+
spreadsheet_packs.append(
|
|
122
|
+
Result(
|
|
123
|
+
filename=filename,
|
|
124
|
+
df_name=df_name,
|
|
125
|
+
df=df,
|
|
126
|
+
filepath=filepath,
|
|
127
|
+
db_alias="",
|
|
128
|
+
platform="",
|
|
129
|
+
sql="",
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
df_name_list.append(df_name)
|
|
133
|
+
readout.print(f" loaded dataframe '{df_name}'")
|
|
134
|
+
|
|
135
|
+
stop = datetime.now()
|
|
136
|
+
readout.print(
|
|
137
|
+
f"{len(spreadsheet_packs)} dataframes loaded from {len(filepaths)} files in {calculate_runtime(start, stop).message}"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Check for df name duplicates
|
|
141
|
+
for df_name in df_name_list:
|
|
142
|
+
df_name_count = df_name_list.count(df_name)
|
|
143
|
+
if df_name_count != 1:
|
|
144
|
+
df_name_duplicate_count += df_name_count - 1
|
|
145
|
+
df_name_duplicates.add(df_name)
|
|
146
|
+
|
|
147
|
+
if df_name_duplicates:
|
|
148
|
+
s = "" if len(df_name_duplicates) == 1 else "s"
|
|
149
|
+
readout.print(
|
|
150
|
+
f"\nWARNING: {len(df_name_duplicates)} duplicate dataframe name{s}:"
|
|
151
|
+
)
|
|
152
|
+
for duplicate in df_name_duplicates:
|
|
153
|
+
readout.warn(f"' -> '{duplicate}")
|
|
154
|
+
|
|
155
|
+
readout.print("")
|
|
156
|
+
return spreadsheet_packs
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _restore_parsed_dates(df: pd.DataFrame, columns: list[str | int]) -> None:
|
|
160
|
+
for position, name in enumerate(df.columns):
|
|
161
|
+
if name in columns or position in columns:
|
|
162
|
+
df.isetitem(position, df.iloc[:, position].infer_objects().array)
|
|
@@ -56,8 +56,8 @@ def run(
|
|
|
56
56
|
username (str, optional): If entered, downstream execution will attempt to use this username. If left
|
|
57
57
|
blank, Kraken will use the default username saved for each given database alias in credential manager.
|
|
58
58
|
Defaults to None.
|
|
59
|
-
clean_df (bool):
|
|
60
|
-
|
|
59
|
+
clean_df (bool): Preserves fetched scalar types during DataFrame construction, including
|
|
60
|
+
nullable integers/booleans without a float intermediate. False uses ordinary pandas inference.
|
|
61
61
|
export_directory (str, optional): Sets a directory to export results to. If left blank, Kraken ignores
|
|
62
62
|
exporting. Enter "" to export to directory of expecting script. Defaults to None.
|
|
63
63
|
export_extension (str, optional): If exporting, Kraken will export as this extension if supported.
|
|
@@ -53,8 +53,8 @@ def execute(
|
|
|
53
53
|
username (str, optional): If blank, Kraken will use the default username for the given alias. Otherwise,
|
|
54
54
|
Kraken will fetch saved credentials for the given username/database alias pair. Defaults to None.
|
|
55
55
|
batch_size (int): Downloads rows in batches. Use `batch_size=0` to fetch all data without batching.
|
|
56
|
-
clean_df (bool):
|
|
57
|
-
|
|
56
|
+
clean_df (bool): Preserves fetched scalar types during DataFrame construction, including
|
|
57
|
+
nullable integers/booleans without a float intermediate. False uses ordinary pandas inference.
|
|
58
58
|
isolation_level (Literal["SERIALIZABLE", "REPEATABLE READ", "READ COMMITTED", "READ UNCOMMITTED", "AUTOCOMMIT"] | None):
|
|
59
59
|
SQL Alchemy isolation level. Defaults to None. If errors are raised related to not being able to perform
|
|
60
60
|
queries within transactions, (for example as typical with Synapse databases), try using "AUTOCOMMIT".
|
|
@@ -152,8 +152,8 @@ def execute_sql(
|
|
|
152
152
|
query_list (list[Query]): List of QueryList of Query objects prepared by extract_sql()
|
|
153
153
|
username (str, optional): Overriding username to fetch database credentials. Defaults to None. WARNING:
|
|
154
154
|
Kraken will attempt to use this username to execute all queries.
|
|
155
|
-
clean_df (bool):
|
|
156
|
-
|
|
155
|
+
clean_df (bool): Preserves fetched scalar types during DataFrame construction, including
|
|
156
|
+
nullable integers/booleans without a float intermediate. False uses ordinary pandas inference.
|
|
157
157
|
batch_size (int): Downloads rows in batches. Use `batch_size=0` to fetch all data without batching.
|
|
158
158
|
concurrent (bool): If True, executes all SQL files concurrently. Queries within each SQL file will still
|
|
159
159
|
execute sequentially. Defaults to False.
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import pandas as pd
|
|
6
|
-
|
|
7
|
-
from kraken.analysis.data_manipulation import check_df_integers as clean_dataframe
|
|
8
|
-
from kraken.classes.pack_lists import ResultList
|
|
9
|
-
from kraken.classes.packs import Result
|
|
10
|
-
from kraken.support.readout import readout
|
|
11
|
-
from kraken.support.support import _check_filetype, _load_filepaths, calculate_runtime
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def extract_spreadsheets(
|
|
15
|
-
filepaths: str | list[str] | Path | list[Path], clean_df: bool = True, **kwargs: Any
|
|
16
|
-
) -> ResultList:
|
|
17
|
-
"""Takes paths, or list of paths, to spreadsheet files or directories of spreadsheet
|
|
18
|
-
files and outputs a list of results with dataframes. Outputs list in order of user
|
|
19
|
-
directory/file input. Raises errors if a filepath does not point to a spreadsheet
|
|
20
|
-
file or a valid directory, or if no spreadsheet files are detected. Raises warnings
|
|
21
|
-
if any given directory filepath returns no files.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
filepaths (str | list[str]): Path or list of paths to spreadsheet files or
|
|
25
|
-
directories of spreadsheet files
|
|
26
|
-
clean_df (bool): Checks DataFrame after pandas generation and applies cleaning,
|
|
27
|
-
including converting float64 to Int64 if applicable (recommended).
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
list: List of results, consisting of tuples as: (filename, df name, dataframe, df, filepath)
|
|
31
|
-
"""
|
|
32
|
-
start = datetime.now()
|
|
33
|
-
supported_extensions = ["csv", "xlsx", "xls"]
|
|
34
|
-
keep_default_na = False
|
|
35
|
-
na_values = [""]
|
|
36
|
-
|
|
37
|
-
# Load Filepaths
|
|
38
|
-
filepaths = _load_filepaths(filepaths, supported_extensions)
|
|
39
|
-
spreadsheet_packs = ResultList()
|
|
40
|
-
df_name_list = []
|
|
41
|
-
df_name_duplicates = set()
|
|
42
|
-
df_name_duplicate_count = 0
|
|
43
|
-
|
|
44
|
-
readout.print("Loading dataframes from spreadsheets...")
|
|
45
|
-
# Load spreadsheets
|
|
46
|
-
for filepath in filepaths:
|
|
47
|
-
# Load csv dataframes
|
|
48
|
-
if _check_filetype(filepath, "csv"):
|
|
49
|
-
filename = Path(filepath).name
|
|
50
|
-
df_name = Path(filepath).stem
|
|
51
|
-
readout.print(f" - From csv '{filename}'...", end="")
|
|
52
|
-
df = pd.read_csv(
|
|
53
|
-
filepath,
|
|
54
|
-
keep_default_na=keep_default_na,
|
|
55
|
-
na_values=na_values,
|
|
56
|
-
**kwargs,
|
|
57
|
-
)
|
|
58
|
-
spreadsheet_packs.append(
|
|
59
|
-
Result(
|
|
60
|
-
filename=filename,
|
|
61
|
-
df_name=df_name,
|
|
62
|
-
df=df,
|
|
63
|
-
filepath=filepath,
|
|
64
|
-
db_alias="",
|
|
65
|
-
platform="",
|
|
66
|
-
sql="",
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
df_name_list.append(df_name)
|
|
70
|
-
readout.print(f" loaded dataframe '{df_name}'")
|
|
71
|
-
|
|
72
|
-
# Load xlsx dataframes
|
|
73
|
-
if _check_filetype(filepath, ["xlsx", "xls"]):
|
|
74
|
-
filename = Path(filepath).name
|
|
75
|
-
readout.print(f" - From xlsx '{filename}'...")
|
|
76
|
-
with pd.ExcelFile(filepath) as file:
|
|
77
|
-
for sheet_name in file.sheet_names:
|
|
78
|
-
df_name = str(sheet_name)
|
|
79
|
-
df = file.parse(
|
|
80
|
-
df_name,
|
|
81
|
-
keep_default_na=keep_default_na,
|
|
82
|
-
na_values=na_values,
|
|
83
|
-
**kwargs,
|
|
84
|
-
)
|
|
85
|
-
spreadsheet_packs.append(
|
|
86
|
-
Result(
|
|
87
|
-
filename=filename,
|
|
88
|
-
df_name=df_name,
|
|
89
|
-
df=df,
|
|
90
|
-
filepath=filepath,
|
|
91
|
-
db_alias="",
|
|
92
|
-
platform="",
|
|
93
|
-
sql="",
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
df_name_list.append(df_name)
|
|
97
|
-
readout.print(f" - loaded dataframe '{df_name}'")
|
|
98
|
-
|
|
99
|
-
# Clean SpreadsheetPack DataFrames
|
|
100
|
-
if clean_df:
|
|
101
|
-
for pack in spreadsheet_packs:
|
|
102
|
-
pack.df = clean_dataframe(pack.df)
|
|
103
|
-
|
|
104
|
-
stop = datetime.now()
|
|
105
|
-
readout.print(
|
|
106
|
-
f"{len(spreadsheet_packs)} dataframes loaded from {len(filepaths)} files in {calculate_runtime(start, stop).message}"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Check for df name duplicates
|
|
110
|
-
for df_name in df_name_list:
|
|
111
|
-
df_name_count = df_name_list.count(df_name)
|
|
112
|
-
if df_name_count != 1:
|
|
113
|
-
df_name_duplicate_count += df_name_count - 1
|
|
114
|
-
df_name_duplicates.add(df_name)
|
|
115
|
-
|
|
116
|
-
if df_name_duplicates:
|
|
117
|
-
s = "" if len(df_name_duplicates) == 1 else "s"
|
|
118
|
-
readout.print(
|
|
119
|
-
f"\nWARNING: {len(df_name_duplicates)} duplicate dataframe name{s}:"
|
|
120
|
-
)
|
|
121
|
-
for duplicate in df_name_duplicates:
|
|
122
|
-
readout.warn(f"' -> '{duplicate}")
|
|
123
|
-
|
|
124
|
-
readout.print("")
|
|
125
|
-
return spreadsheet_packs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|