analytics-toolkit 1.3.6.18__tar.gz → 1.3.7.3__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 (148) hide show
  1. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/PKG-INFO +2 -2
  2. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/README.md +1 -1
  3. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/connection/config.py +88 -36
  4. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/connection/get_sql_connection.py +80 -77
  5. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/PKG-INFO +2 -2
  6. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/pyproject.toml +1 -1
  7. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_connection_config.py +344 -13
  8. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/LICENSE +0 -0
  9. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/__init__.py +0 -0
  10. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/__init__.py +0 -0
  11. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/api.py +0 -0
  12. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/bootstrap.py +0 -0
  13. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/constants.py +0 -0
  14. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/cuped.py +0 -0
  15. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/formatter.py +0 -0
  16. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/metrics.py +0 -0
  17. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/outliers.py +0 -0
  18. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/parallel.py +0 -0
  19. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/planning.py +0 -0
  20. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/ratio.py +0 -0
  21. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/rows.py +0 -0
  22. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/split.py +0 -0
  23. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/stats.py +0 -0
  24. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/ab_utils/validation.py +0 -0
  25. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/cli.py +0 -0
  26. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/dates/__init__.py +0 -0
  27. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/dates/dates.py +0 -0
  28. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/dates/functions.py +0 -0
  29. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/excel/__init__.py +0 -0
  30. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/excel/long_format.py +0 -0
  31. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/general/__init__.py +0 -0
  32. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/general/logging.py +0 -0
  33. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/general/read_file.py +0 -0
  34. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/__init__.py +0 -0
  35. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/__init__.py +0 -0
  36. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/base.py +0 -0
  37. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/clickhouse.py +0 -0
  38. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/dbapi.py +0 -0
  39. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/gp.py +0 -0
  40. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/registry.py +0 -0
  41. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/trino.py +0 -0
  42. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/_backend_adapters/utils.py +0 -0
  43. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/backend_adapters.py +0 -0
  44. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/clickhouse/__init__.py +0 -0
  45. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/clickhouse/lifecycle.py +0 -0
  46. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/clickhouse/options.py +0 -0
  47. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/clickhouse/wait.py +0 -0
  48. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/connection/__init__.py +0 -0
  49. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/connection/errors.py +0 -0
  50. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/connection/protocols.py +0 -0
  51. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/core/__init__.py +0 -0
  52. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/core/capabilities.py +0 -0
  53. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/core/identifiers.py +0 -0
  54. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/core/types.py +0 -0
  55. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/__init__.py +0 -0
  56. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/api.py +0 -0
  57. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/builders.py +0 -0
  58. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/clickhouse.py +0 -0
  59. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/extract_ddl.py +0 -0
  60. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/identifiers.py +0 -0
  61. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/models.py +0 -0
  62. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/ddl/schema.py +0 -0
  63. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/__init__.py +0 -0
  64. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/__init__.py +0 -0
  65. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/execute_read.py +0 -0
  66. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/execute_sql.py +0 -0
  67. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/gp_cancel.py +0 -0
  68. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/models.py +0 -0
  69. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/io/read_sql.py +0 -0
  70. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/load/__init__.py +0 -0
  71. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/load/load_df.py +0 -0
  72. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/load/load_sql_table.py +0 -0
  73. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/load/models.py +0 -0
  74. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/load/stage.py +0 -0
  75. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/__init__.py +0 -0
  76. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/_basic_ops.py +0 -0
  77. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/api.py +0 -0
  78. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/ch_create_table_as.py +0 -0
  79. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/ch_drop_table.py +0 -0
  80. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/ch_full_table_move.py +0 -0
  81. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/create_table_from_sql.py +0 -0
  82. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/maintenance.py +0 -0
  83. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/models.py +0 -0
  84. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/partitions.py +0 -0
  85. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/table_validation.py +0 -0
  86. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/table/write_modes.py +0 -0
  87. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/__init__.py +0 -0
  88. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/__init__.py +0 -0
  89. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/api.py +0 -0
  90. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/attempt.py +0 -0
  91. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/estimate.py +0 -0
  92. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/finalize.py +0 -0
  93. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/flow/stage.py +0 -0
  94. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/io/__init__.py +0 -0
  95. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/io/source.py +0 -0
  96. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/runtime/__init__.py +0 -0
  97. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/runtime/models.py +0 -0
  98. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/runtime/retry.py +0 -0
  99. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/dml/transfer/schema.py +0 -0
  100. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/__init__.py +0 -0
  101. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/labels.py +0 -0
  102. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/operation_runner.py +0 -0
  103. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/plan_steps.py +0 -0
  104. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/plans.py +0 -0
  105. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/execution/query_timing.py +0 -0
  106. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/metadata/__init__.py +0 -0
  107. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/metadata/show_tables.py +0 -0
  108. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/metadata/table_info.py +0 -0
  109. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/orchestration/__init__.py +0 -0
  110. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/orchestration/async_sql.py +0 -0
  111. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/orchestration/parallel_sql.py +0 -0
  112. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/orchestration/pipeline.py +0 -0
  113. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit/sql/orchestration/tasks.py +0 -0
  114. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/SOURCES.txt +0 -0
  115. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/dependency_links.txt +0 -0
  116. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/entry_points.txt +0 -0
  117. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/requires.txt +0 -0
  118. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/analytics_toolkit.egg-info/top_level.txt +0 -0
  119. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/setup.cfg +0 -0
  120. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_ab_utils_format.py +0 -0
  121. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_ab_utils_metrics.py +0 -0
  122. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_ab_utils_parallel.py +0 -0
  123. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_ab_utils_split.py +0 -0
  124. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_dates.py +0 -0
  125. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_excel_long_format.py +0 -0
  126. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_general_logging.py +0 -0
  127. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_general_read_file.py +0 -0
  128. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_architecture.py +0 -0
  129. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_async.py +0 -0
  130. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_backend_adapters.py +0 -0
  131. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_ch_create_table_as.py +0 -0
  132. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_ch_drop_table.py +0 -0
  133. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_ch_full_table_move.py +0 -0
  134. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_create_table_from_sql.py +0 -0
  135. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_drop_many_partitions.py +0 -0
  136. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_execute_read.py +0 -0
  137. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_extract_ddl.py +0 -0
  138. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_gp_cancel.py +0 -0
  139. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_gp_create_partitions.py +0 -0
  140. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_improvements.py +0 -0
  141. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_load_table.py +0 -0
  142. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_parallel.py +0 -0
  143. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_retries.py +0 -0
  144. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_show_tables.py +0 -0
  145. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_transfer_clickhouse.py +0 -0
  146. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_transfer_row_batches.py +0 -0
  147. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_transfer_schema.py +0 -0
  148. {analytics_toolkit-1.3.6.18 → analytics_toolkit-1.3.7.3}/tests/test_sql_transfer_source_batches.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analytics-toolkit
3
- Version: 1.3.6.18
3
+ Version: 1.3.7.3
4
4
  Summary: Shared Python utilities for SQL, Excel, and date helpers.
5
5
  Author: analytics_toolkit contributors
6
6
  License: MIT License
@@ -75,7 +75,7 @@ Python toolkit for AB-test analysis, SQL workflows, Excel reports, and date help
75
75
 
76
76
  [Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
77
77
 
78
- **Version:** `1.3.6.18`<br>
78
+ **Version:** `1.3.7.3`<br>
79
79
  **Depends:** Python (`>=3.8,<3.15`)<br>
80
80
  **Imports:** [clickhouse-connect](https://pypi.org/project/clickhouse-connect/) (`>=0.5.14,<1`), [lz4](https://pypi.org/project/lz4/) (`>=4.3.2,<5`), [numpy](https://pypi.org/project/numpy/) (`>=1.24.2,<2`), [openpyxl](https://pypi.org/project/openpyxl/) (`>=3.1.1,<4`), [orjson](https://pypi.org/project/orjson/) (`>=3.8.7,<4`), [pandas](https://pypi.org/project/pandas/) (`>=1.4.4,<3`), [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) (`>=2.9.5,<3`), [python-dateutil](https://pypi.org/project/python-dateutil/) (`>=2.8.2,<3`), [pytz](https://pypi.org/project/pytz/) (`>=2022.7`), [requests](https://pypi.org/project/requests/) (`>=2.28.2,<3`), [scipy](https://pypi.org/project/scipy/) (`>=1.10.1,<2`), [sqlglot](https://pypi.org/project/sqlglot/) (`>=20,<31`), [sqlparse](https://pypi.org/project/sqlparse/) (`>=0.4.3,<1`), [tqdm](https://pypi.org/project/tqdm/) (`>=4.65.0,<5`), [trino](https://pypi.org/project/trino/) (`>=0.320,<1`), [zstandard](https://pypi.org/project/zstandard/) (`>=0.20.0,<1`)<br>
81
81
  **Suggests:** [apache-airflow](https://pypi.org/project/apache-airflow/) (`>=2.4,<3`; optional extra `airflow`)<br>
@@ -6,7 +6,7 @@ Python toolkit for AB-test analysis, SQL workflows, Excel reports, and date help
6
6
 
7
7
  [Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
8
8
 
9
- **Version:** `1.3.6.18`<br>
9
+ **Version:** `1.3.7.3`<br>
10
10
  **Depends:** Python (`>=3.8,<3.15`)<br>
11
11
  **Imports:** [clickhouse-connect](https://pypi.org/project/clickhouse-connect/) (`>=0.5.14,<1`), [lz4](https://pypi.org/project/lz4/) (`>=4.3.2,<5`), [numpy](https://pypi.org/project/numpy/) (`>=1.24.2,<2`), [openpyxl](https://pypi.org/project/openpyxl/) (`>=3.1.1,<4`), [orjson](https://pypi.org/project/orjson/) (`>=3.8.7,<4`), [pandas](https://pypi.org/project/pandas/) (`>=1.4.4,<3`), [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) (`>=2.9.5,<3`), [python-dateutil](https://pypi.org/project/python-dateutil/) (`>=2.8.2,<3`), [pytz](https://pypi.org/project/pytz/) (`>=2022.7`), [requests](https://pypi.org/project/requests/) (`>=2.28.2,<3`), [scipy](https://pypi.org/project/scipy/) (`>=1.10.1,<2`), [sqlglot](https://pypi.org/project/sqlglot/) (`>=20,<31`), [sqlparse](https://pypi.org/project/sqlparse/) (`>=0.4.3,<1`), [tqdm](https://pypi.org/project/tqdm/) (`>=4.65.0,<5`), [trino](https://pypi.org/project/trino/) (`>=0.320,<1`), [zstandard](https://pypi.org/project/zstandard/) (`>=0.20.0,<1`)<br>
12
12
  **Suggests:** [apache-airflow](https://pypi.org/project/apache-airflow/) (`>=2.4,<3`; optional extra `airflow`)<br>
@@ -56,8 +56,7 @@ class TrinoConfig:
56
56
  auth_mode: str
57
57
  http_scheme: str
58
58
  verify_value: str
59
- use_keychain_certs: bool
60
- keychain_cert_names: list[str]
59
+ ca_certs: list[str]
61
60
  insert_chunk_size: int | None
62
61
  request_timeout: int | None
63
62
  source: str | None
@@ -77,6 +76,10 @@ class GpConfig:
77
76
  keepalives_idle: int
78
77
  keepalives_interval: int
79
78
  keepalives_count: int
79
+ sslmode: str | None
80
+ ca_certs: list[str]
81
+ ssl_cert: str | None
82
+ ssl_key: str | None
80
83
 
81
84
 
82
85
  @dataclass(frozen=True)
@@ -90,8 +93,8 @@ class ChConfig:
90
93
  database: str | None
91
94
  secure: bool
92
95
  verify_value: str | None
93
- ca_cert: str | None
94
- ca_cert_variable: str | None
96
+ ca_certs: list[str]
97
+ ca_certs_variable: str | None
95
98
  connect_timeout: int | None
96
99
  send_receive_timeout: int | None
97
100
  settings: dict[str, Any] | None
@@ -174,6 +177,11 @@ def _build_connection_config(
174
177
  backend = _require_backend(connection_key, raw_config)
175
178
 
176
179
  if backend == "trino":
180
+ _reject_removed_fields(
181
+ raw_config,
182
+ connection_key,
183
+ ["use_keychain_certs", "keychain_cert_names", "ca_cert"],
184
+ )
177
185
  return TrinoConfig(
178
186
  connection_key=connection_key,
179
187
  backend=backend,
@@ -203,16 +211,10 @@ def _build_connection_config(
203
211
  )
204
212
  or "true"
205
213
  ),
206
- use_keychain_certs=_optional_bool(
214
+ ca_certs=_optional_string_or_string_list(
207
215
  raw_config,
208
216
  connection_key,
209
- "use_keychain_certs",
210
- False,
211
- ),
212
- keychain_cert_names=_optional_string_list(
213
- raw_config,
214
- connection_key,
215
- "keychain_cert_names",
217
+ "ca_certs",
216
218
  ),
217
219
  insert_chunk_size=_optional_positive_int(
218
220
  raw_config,
@@ -227,6 +229,15 @@ def _build_connection_config(
227
229
  source=_optional_string(raw_config, connection_key, "source"),
228
230
  )
229
231
  if backend == "gp":
232
+ _reject_removed_fields(raw_config, connection_key, ["ca_cert"])
233
+ ca_certs = _optional_string_or_string_list(
234
+ raw_config,
235
+ connection_key,
236
+ "ca_certs",
237
+ )
238
+ sslmode = _optional_string(raw_config, connection_key, "sslmode")
239
+ if ca_certs and sslmode is None:
240
+ sslmode = "verify-full"
230
241
  return GpConfig(
231
242
  connection_key=connection_key,
232
243
  backend=backend,
@@ -265,24 +276,29 @@ def _build_connection_config(
265
276
  "keepalives_count",
266
277
  DEFAULT_GP_KEEPALIVES_COUNT,
267
278
  ),
279
+ sslmode=sslmode,
280
+ ca_certs=ca_certs,
281
+ ssl_cert=_optional_string(raw_config, connection_key, "ssl_cert"),
282
+ ssl_key=_optional_string(raw_config, connection_key, "ssl_key"),
268
283
  )
269
284
  if backend == "ch":
270
- ca_cert = _optional_string(raw_config, connection_key, "ca_cert")
271
- if ca_cert is None:
272
- ca_cert = _optional_string(raw_config, connection_key, "ca_certs")
273
- ca_cert_variable = _optional_string(
285
+ _reject_removed_fields(
274
286
  raw_config,
275
287
  connection_key,
276
- "ca_cert_variable",
288
+ ["ca_cert", "ca_cert_variable"],
277
289
  )
278
- if ca_cert_variable is None:
279
- ca_cert_variable = _optional_string(
280
- raw_config,
281
- connection_key,
282
- "ca_certs_variable",
283
- )
284
- if ca_cert is not None:
285
- ca_cert_variable = None
290
+ ca_certs = _optional_string_or_string_list(
291
+ raw_config,
292
+ connection_key,
293
+ "ca_certs",
294
+ )
295
+ ca_certs_variable = _optional_string(
296
+ raw_config,
297
+ connection_key,
298
+ "ca_certs_variable",
299
+ )
300
+ if ca_certs:
301
+ ca_certs_variable = None
286
302
  return ChConfig(
287
303
  connection_key=connection_key,
288
304
  backend=backend,
@@ -297,8 +313,8 @@ def _build_connection_config(
297
313
  connection_key,
298
314
  "verify",
299
315
  ),
300
- ca_cert=ca_cert,
301
- ca_cert_variable=ca_cert_variable,
316
+ ca_certs=ca_certs,
317
+ ca_certs_variable=ca_certs_variable,
302
318
  connect_timeout=_optional_positive_int(
303
319
  raw_config,
304
320
  connection_key,
@@ -338,19 +354,35 @@ def generate_dummy_connections(airflow: bool = False) -> Path:
338
354
  connections_path = Path.cwd() / CONNECTIONS_FILE_NAME
339
355
  if connections_path.exists():
340
356
  raise ValueError(f"SQL connections file already exists: {connections_path}")
357
+ certs_dir = Path.cwd() / ".certs"
341
358
 
342
359
  content = (
343
360
  _build_dummy_airflow_connections()
344
361
  if airflow
345
362
  else _build_dummy_direct_connections()
346
363
  )
364
+ certs_dir.mkdir(exist_ok=True)
347
365
  connections_path.write_text(
348
366
  json.dumps(content, indent=2) + "\n",
349
367
  encoding="utf-8",
350
368
  )
369
+ _print_dummy_connections_cert_instructions(certs_dir)
351
370
  return connections_path
352
371
 
353
372
 
373
+ def _print_dummy_connections_cert_instructions(certs_dir: Path) -> None:
374
+ print(f"Created {certs_dir} for local certificate files.")
375
+ print(
376
+ "Greenplum: put CA PEMs in .certs/ and set ca_certs; "
377
+ "optional ssl_cert, ssl_key, sslmode."
378
+ )
379
+ print("Trino: put HTTPS CA PEMs in .certs/ and set ca_certs.")
380
+ print(
381
+ "ClickHouse: put HTTPS CA PEMs in .certs/ and set ca_certs; "
382
+ "Airflow can use ca_certs_variable."
383
+ )
384
+
385
+
354
386
  def _build_dummy_direct_connections() -> dict[str, dict[str, Any]]:
355
387
  return {
356
388
  "gp": {
@@ -360,6 +392,7 @@ def _build_dummy_direct_connections() -> dict[str, dict[str, Any]]:
360
392
  "user": "user",
361
393
  "password": "password",
362
394
  "database": "db",
395
+ "ca_certs": "gp-ca.pem",
363
396
  },
364
397
  "trino": {
365
398
  "type": "trino",
@@ -369,6 +402,8 @@ def _build_dummy_direct_connections() -> dict[str, dict[str, Any]]:
369
402
  "password": "password",
370
403
  "catalog": "iceberg",
371
404
  "schema": "sandbox",
405
+ "http_scheme": "https",
406
+ "ca_certs": "trino-ca.pem",
372
407
  },
373
408
  "ch": {
374
409
  "type": "ch",
@@ -377,6 +412,8 @@ def _build_dummy_direct_connections() -> dict[str, dict[str, Any]]:
377
412
  "user": "user",
378
413
  "password": "password",
379
414
  "database": "default",
415
+ "secure": True,
416
+ "ca_certs": "clickhouse-ca.pem",
380
417
  },
381
418
  }
382
419
 
@@ -385,9 +422,9 @@ def _build_dummy_airflow_connections() -> dict[str, Any]:
385
422
  return {
386
423
  "source": "airflow",
387
424
  "connections": {
388
- "gp": {"type": "gp"},
389
- "trino": {"type": "trino"},
390
- "ch": {"type": "ch"},
425
+ "gp": {"type": "gp", "ca_certs": "gp-ca.pem"},
426
+ "trino": {"type": "trino", "ca_certs": "trino-ca.pem"},
427
+ "ch": {"type": "ch", "ca_certs": "clickhouse-ca.pem"},
391
428
  },
392
429
  }
393
430
 
@@ -763,6 +800,10 @@ def _get_airflow_raw_connection_config_and_extras(
763
800
  "keepalives_idle",
764
801
  "keepalives_interval",
765
802
  "keepalives_count",
803
+ "sslmode",
804
+ "ca_certs",
805
+ "ssl_cert",
806
+ "ssl_key",
766
807
  ],
767
808
  )
768
809
  elif resolved_backend == "trino":
@@ -775,8 +816,7 @@ def _get_airflow_raw_connection_config_and_extras(
775
816
  "auth_mode",
776
817
  "http_scheme",
777
818
  "verify",
778
- "use_keychain_certs",
779
- "keychain_cert_names",
819
+ "ca_certs",
780
820
  "insert_chunk_size",
781
821
  "request_timeout",
782
822
  "source",
@@ -794,9 +834,7 @@ def _get_airflow_raw_connection_config_and_extras(
794
834
  [
795
835
  "secure",
796
836
  "verify",
797
- "ca_cert",
798
837
  "ca_certs",
799
- "ca_cert_variable",
800
838
  "ca_certs_variable",
801
839
  "connect_timeout",
802
840
  "send_receive_timeout",
@@ -1208,7 +1246,21 @@ def _optional_mapping(
1208
1246
  return dict(value)
1209
1247
 
1210
1248
 
1211
- def _optional_string_list(
1249
+ def _reject_removed_fields(
1250
+ config: dict[str, Any],
1251
+ connection_key: str,
1252
+ field_names: Sequence[str],
1253
+ ) -> None:
1254
+ for field_name in field_names:
1255
+ if field_name in config:
1256
+ raise SqlConfigError(
1257
+ f"SQL connection '{connection_key}' field '{field_name}' "
1258
+ "is not supported. "
1259
+ "Use 'ca_certs' for CA certificate files."
1260
+ )
1261
+
1262
+
1263
+ def _optional_string_or_string_list(
1212
1264
  config: dict[str, Any],
1213
1265
  connection_key: str,
1214
1266
  field_name: str,
@@ -1217,7 +1269,7 @@ def _optional_string_list(
1217
1269
  if value is None:
1218
1270
  return []
1219
1271
  if isinstance(value, str):
1220
- names = [name.strip() for name in value.split("|")]
1272
+ names = [value.strip()]
1221
1273
  elif isinstance(value, list):
1222
1274
  names = []
1223
1275
  for item in value:
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
- import subprocess
5
3
  from dataclasses import replace
6
4
  from functools import wraps
7
5
  from pathlib import Path
@@ -91,9 +89,8 @@ def _get_trino_connection(config: TrinoConfig) -> Any:
91
89
  "The 'trino' package is required for Trino connections."
92
90
  ) from exc
93
91
 
94
- verify_value = config.verify_value
95
- if config.use_keychain_certs:
96
- verify_value = str(_build_trino_keychain_bundle(config))
92
+ ca_certs = _resolve_ca_certs(config.connection_key, config.ca_certs)
93
+ verify_value = ca_certs or config.verify_value
97
94
 
98
95
  if config.auth_mode == "oauth2":
99
96
  auth = trino.auth.OAuth2Authentication()
@@ -150,6 +147,25 @@ def _get_gp_connection(config: GpConfig) -> Any:
150
147
  "keepalives_count": config.keepalives_count,
151
148
  }
152
149
  )
150
+ if config.sslmode is not None:
151
+ connect_kwargs["sslmode"] = config.sslmode
152
+ ca_certs = _resolve_ca_certs(config.connection_key, config.ca_certs)
153
+ if ca_certs is not None:
154
+ connect_kwargs["sslrootcert"] = ca_certs
155
+ if config.ssl_cert is not None:
156
+ ssl_cert = _resolve_single_cert_path(
157
+ config.connection_key,
158
+ config.ssl_cert,
159
+ field_name="ssl_cert",
160
+ )
161
+ connect_kwargs["sslcert"] = str(ssl_cert)
162
+ if config.ssl_key is not None:
163
+ ssl_key = _resolve_single_cert_path(
164
+ config.connection_key,
165
+ config.ssl_key,
166
+ field_name="ssl_key",
167
+ )
168
+ connect_kwargs["sslkey"] = str(ssl_key)
153
169
 
154
170
  return psycopg2.connect(**connect_kwargs)
155
171
 
@@ -176,9 +192,9 @@ def _get_ch_connection(config: ChConfig) -> Any:
176
192
  client_kwargs["database"] = config.database
177
193
  if config.verify_value is not None:
178
194
  client_kwargs["verify"] = _parse_verify_value(config.verify_value)
179
- ca_cert = _resolve_ch_ca_cert(config)
180
- if ca_cert is not None:
181
- client_kwargs["ca_cert"] = ca_cert
195
+ ca_certs = _resolve_ch_ca_certs(config)
196
+ if ca_certs is not None:
197
+ client_kwargs["ca_cert"] = ca_certs
182
198
  if config.connect_timeout is not None:
183
199
  client_kwargs["connect_timeout"] = config.connect_timeout
184
200
  if config.send_receive_timeout is not None:
@@ -197,35 +213,35 @@ def _get_ch_connection(config: ChConfig) -> Any:
197
213
  return clickhouse_connect.get_client(**client_kwargs)
198
214
 
199
215
 
200
- def _resolve_ch_ca_cert(config: ChConfig) -> str | None:
201
- if config.ca_cert:
202
- return config.ca_cert
203
- if config.ca_cert_variable is None:
216
+ def _resolve_ch_ca_certs(config: ChConfig) -> str | None:
217
+ if config.ca_certs:
218
+ return _resolve_ca_certs(config.connection_key, config.ca_certs)
219
+ if config.ca_certs_variable is None:
204
220
  return None
205
221
 
206
222
  try:
207
223
  from airflow.models.variable import Variable
208
224
  except ImportError as exc:
209
225
  raise SqlConfigError(
210
- f"SQL connection '{config.connection_key}' uses ca_cert_variable "
226
+ f"SQL connection '{config.connection_key}' uses ca_certs_variable "
211
227
  "but Airflow Variable support is unavailable."
212
228
  ) from exc
213
229
 
214
230
  try:
215
- ca_cert = Variable.get(config.ca_cert_variable)
231
+ ca_certs = Variable.get(config.ca_certs_variable)
216
232
  except Exception as exc:
217
233
  raise SqlConfigError(
218
- f"Could not resolve Airflow Variable '{config.ca_cert_variable}' "
234
+ f"Could not resolve Airflow Variable '{config.ca_certs_variable}' "
219
235
  f"for SQL connection '{config.connection_key}'. "
220
236
  f"{type(exc).__name__}: {exc}"
221
237
  ) from exc
222
238
 
223
- if not isinstance(ca_cert, str) or not ca_cert.strip():
239
+ if not isinstance(ca_certs, str) or not ca_certs.strip():
224
240
  raise SqlConfigError(
225
- f"Airflow Variable '{config.ca_cert_variable}' for SQL connection "
241
+ f"Airflow Variable '{config.ca_certs_variable}' for SQL connection "
226
242
  f"'{config.connection_key}' must be a non-empty string."
227
243
  )
228
- return ca_cert.strip()
244
+ return _resolve_ca_certs(config.connection_key, [ca_certs.strip()])
229
245
 
230
246
 
231
247
  def _parse_verify_value(value: str) -> bool | str:
@@ -238,69 +254,56 @@ def _parse_verify_value(value: str) -> bool | str:
238
254
  return normalized
239
255
 
240
256
 
241
- def _build_trino_keychain_bundle(config: TrinoConfig) -> Path:
242
- if not config.keychain_cert_names:
243
- raise SqlConfigError(
244
- f"SQL connection '{config.connection_key}' enables keychain certs "
245
- "but does not define keychain_cert_names."
246
- )
257
+ def _resolve_ca_certs(connection_key: str, ca_certs: list[str]) -> str | None:
258
+ if not ca_certs:
259
+ return None
247
260
 
248
- certs_dir = _state_dir() / "certs"
249
- certs_dir.mkdir(parents=True, exist_ok=True)
250
- bundle_path = certs_dir / f"trino-{_safe_file_key(config.connection_key)}-keychain-ca.pem"
251
- keychains = [
252
- str(Path.home() / "Library/Keychains/login.keychain-db"),
253
- "/Library/Keychains/System.keychain",
261
+ resolved_paths = [
262
+ _resolve_single_cert_path(connection_key, cert_path, field_name="ca_certs")
263
+ for cert_path in ca_certs
254
264
  ]
255
-
256
- certificates: list[str] = []
257
- for cert_name in config.keychain_cert_names:
258
- certificate = _export_keychain_certificate(cert_name, keychains)
259
- if not certificate:
260
- raise SqlConfigError(
261
- f"Could not export '{cert_name}' from macOS Keychain."
262
- )
263
- certificates.append(certificate.strip())
264
- bundle_contents = "\n".join(certificates) + "\n"
265
- if not bundle_path.exists() or bundle_path.read_text(encoding="utf-8") != bundle_contents:
265
+ if len(resolved_paths) == 1:
266
+ return str(resolved_paths[0])
267
+
268
+ connections_dir = get_connections_file_path().parent
269
+ bundle_dir = connections_dir / ".certs" / ".generated"
270
+ bundle_dir.mkdir(parents=True, exist_ok=True)
271
+ bundle_path = bundle_dir / f"{_safe_file_key(connection_key)}-ca-bundle.pem"
272
+ bundle_contents = "\n".join(
273
+ path.read_text(encoding="utf-8").strip()
274
+ for path in resolved_paths
275
+ ) + "\n"
276
+ if (
277
+ not bundle_path.exists()
278
+ or bundle_path.read_text(encoding="utf-8") != bundle_contents
279
+ ):
266
280
  bundle_path.write_text(bundle_contents, encoding="utf-8")
267
- return bundle_path
268
-
269
-
270
- def _export_keychain_certificate(cert_name: str, keychains: list[str]) -> str:
271
- for keychain in keychains:
272
- if not Path(keychain).exists():
273
- continue
274
-
275
- result = subprocess.run(
276
- [
277
- "security",
278
- "find-certificate",
279
- "-a",
280
- "-c",
281
- cert_name,
282
- "-p",
283
- keychain,
284
- ],
285
- capture_output=True,
286
- text=True,
287
- check=False,
281
+ return str(bundle_path)
282
+
283
+
284
+ def _resolve_single_cert_path(
285
+ connection_key: str,
286
+ cert_path: str,
287
+ *,
288
+ field_name: str,
289
+ ) -> Path:
290
+ raw_path = cert_path.strip()
291
+ path = Path(raw_path)
292
+ if path.is_absolute():
293
+ resolved = path
294
+ else:
295
+ connections_dir = get_connections_file_path().parent
296
+ if "/" in raw_path or "\\" in raw_path:
297
+ resolved = connections_dir / path
298
+ else:
299
+ resolved = connections_dir / ".certs" / path
300
+ resolved = resolved.resolve()
301
+ if not resolved.is_file():
302
+ raise SqlConfigError(
303
+ f"SQL connection '{connection_key}' field '{field_name}' references "
304
+ f"missing certificate file: {resolved}"
288
305
  )
289
- if result.returncode == 0 and "BEGIN CERTIFICATE" in result.stdout:
290
- return result.stdout
291
-
292
- return ""
293
-
294
-
295
- def _state_dir() -> Path:
296
- state_override = os.getenv("MAGNIT_UTILS_HOME")
297
- if state_override:
298
- return Path(state_override).expanduser()
299
-
300
- try:
301
- return get_connections_file_path().parent
302
- except SqlConfigError:
303
- return Path.cwd().resolve()
306
+ return resolved
304
307
 
305
308
 
306
309
  def _safe_file_key(connection_key: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analytics-toolkit
3
- Version: 1.3.6.18
3
+ Version: 1.3.7.3
4
4
  Summary: Shared Python utilities for SQL, Excel, and date helpers.
5
5
  Author: analytics_toolkit contributors
6
6
  License: MIT License
@@ -75,7 +75,7 @@ Python toolkit for AB-test analysis, SQL workflows, Excel reports, and date help
75
75
 
76
76
  [Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
77
77
 
78
- **Version:** `1.3.6.18`<br>
78
+ **Version:** `1.3.7.3`<br>
79
79
  **Depends:** Python (`>=3.8,<3.15`)<br>
80
80
  **Imports:** [clickhouse-connect](https://pypi.org/project/clickhouse-connect/) (`>=0.5.14,<1`), [lz4](https://pypi.org/project/lz4/) (`>=4.3.2,<5`), [numpy](https://pypi.org/project/numpy/) (`>=1.24.2,<2`), [openpyxl](https://pypi.org/project/openpyxl/) (`>=3.1.1,<4`), [orjson](https://pypi.org/project/orjson/) (`>=3.8.7,<4`), [pandas](https://pypi.org/project/pandas/) (`>=1.4.4,<3`), [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) (`>=2.9.5,<3`), [python-dateutil](https://pypi.org/project/python-dateutil/) (`>=2.8.2,<3`), [pytz](https://pypi.org/project/pytz/) (`>=2022.7`), [requests](https://pypi.org/project/requests/) (`>=2.28.2,<3`), [scipy](https://pypi.org/project/scipy/) (`>=1.10.1,<2`), [sqlglot](https://pypi.org/project/sqlglot/) (`>=20,<31`), [sqlparse](https://pypi.org/project/sqlparse/) (`>=0.4.3,<1`), [tqdm](https://pypi.org/project/tqdm/) (`>=4.65.0,<5`), [trino](https://pypi.org/project/trino/) (`>=0.320,<1`), [zstandard](https://pypi.org/project/zstandard/) (`>=0.20.0,<1`)<br>
81
81
  **Suggests:** [apache-airflow](https://pypi.org/project/apache-airflow/) (`>=2.4,<3`; optional extra `airflow`)<br>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "analytics-toolkit"
7
- version = "1.3.6.18"
7
+ version = "1.3.7.3"
8
8
  description = "Shared Python utilities for SQL, Excel, and date helpers."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8,<3.15"