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.
Files changed (62) hide show
  1. {datakraken-1.7.2 → datakraken-1.7.4}/PKG-INFO +1 -1
  2. {datakraken-1.7.2 → datakraken-1.7.4}/pyproject.toml +1 -1
  3. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/analysis/data_manipulation.py +8 -4
  4. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/connector.py +38 -9
  5. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/result_export.py +7 -1
  6. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/result_export_extensions.py +16 -1
  7. datakraken-1.7.4/src/kraken/importing/data_import.py +162 -0
  8. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/ribosome.py +2 -2
  9. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/sql_execution.py +4 -4
  10. datakraken-1.7.2/src/kraken/importing/data_import.py +0 -125
  11. {datakraken-1.7.2 → datakraken-1.7.4}/.gitignore +0 -0
  12. {datakraken-1.7.2 → datakraken-1.7.4}/LICENSE +0 -0
  13. {datakraken-1.7.2 → datakraken-1.7.4}/README.md +0 -0
  14. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/__init__.py +0 -0
  15. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/analysis/__init__.py +0 -0
  16. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/data_types.py +0 -0
  17. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/pack_lists.py +0 -0
  18. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/classes/packs.py +0 -0
  19. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/__init__.py +0 -0
  20. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/connection/support.py +0 -0
  21. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/__init__.py +0 -0
  22. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/credential_manager.py +0 -0
  23. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/credentials.py +0 -0
  24. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/helpers.py +0 -0
  25. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/integrity.py +0 -0
  26. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/save_connection.py +0 -0
  27. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/credentials/save_convenience.py +0 -0
  28. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/demo/__init__.py +0 -0
  29. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/demo/generate_demo_data.py +0 -0
  30. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exceptions.py +0 -0
  31. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/exporting/__init__.py +0 -0
  32. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/__init__.py +0 -0
  33. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing.py +0 -0
  34. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_graphs.py +0 -0
  35. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_support.py +0 -0
  36. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_support_lists.py +0 -0
  37. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/graphing_theme.py +0 -0
  38. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/graphing/models.py +0 -0
  39. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/importing/__init__.py +0 -0
  40. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/__init__.py +0 -0
  41. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/parsing.py +0 -0
  42. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/scanning.py +0 -0
  43. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/parsing/support_classes.py +0 -0
  44. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/__init__.py +0 -0
  45. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/config.py +0 -0
  46. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/integrity.py +0 -0
  47. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/platforms/oracle/__init__.py +0 -0
  48. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/py.typed +0 -0
  49. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/__init__.py +0 -0
  50. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/sql_extraction.py +0 -0
  51. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/ribosome/support.py +0 -0
  52. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/__init__.py +0 -0
  53. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/progress.py +0 -0
  54. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/readout.py +0 -0
  55. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support.py +0 -0
  56. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support_checks.py +0 -0
  57. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/support_classes.py +0 -0
  58. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/support/warnings.py +0 -0
  59. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/__init__.py +0 -0
  60. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/data_upload.py +0 -0
  61. {datakraken-1.7.2 → datakraken-1.7.4}/src/kraken/uploading/support.py +0 -0
  62. {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.2
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.2"
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].dtype == "object":
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 DataFrame for missed date columns identified as type 'object' by pandas, and converts those found in place.
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 col is None or isinstance(df[col], DataFrame) or df[col].dtype != "object":
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): Cleans DataFrame after pandas generation, including converting float64 to Int64 if
270
- applicable (recommended).
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
- df = clean_dataframe(df) if clean_df else df
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(data=rows, columns=columns)
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): Cleans DataFrame after pandas generation, including converting float64 to Int64 if
630
- applicable (recommended).
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 a supported extension type (csv or xlsx).
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.to_csv(buffer, sep=delimiter, index=False)
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): Checks each DataFrame after pandas generation and applies cleaning, including converting
60
- float64 to Int64 if applicable (recommended).
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): Checks DataFrame after pandas generation and applies cleaning, including converting
57
- float64 to Int64 if applicable (recommended).
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): Checks each DataFrame after pandas generation and applies cleaning, including converting
156
- float64 to Int64 if applicable (recommended).
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