mayutils 2.0.0__tar.gz → 3.0.0__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 (153) hide show
  1. {mayutils-2.0.0 → mayutils-3.0.0}/PKG-INFO +4 -3
  2. {mayutils-2.0.0 → mayutils-3.0.0}/README.md +2 -2
  3. {mayutils-2.0.0 → mayutils-3.0.0}/pyproject.toml +3 -2
  4. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/__init__.py +5 -5
  5. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/data/live.py +100 -57
  6. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/data/queries/__init__.py +71 -47
  7. mayutils-3.0.0/src/mayutils/data/queries/templating.py +223 -0
  8. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/data/read.py +111 -55
  9. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/databases.py +27 -6
  10. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/filesystem/metadata.py +3 -2
  11. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/filesystem/roots.py +1 -1
  12. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/logging.py +1 -1
  13. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/clearing.py +3 -3
  14. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/files.py +33 -25
  15. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/types.py +2 -5
  16. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/utilities.py +9 -3
  17. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/oauth.py +35 -9
  18. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/secrets.py +3 -3
  19. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/webdrivers.py +11 -6
  20. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/export/nbconvert.py +11 -8
  21. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/export/quarto.py +6 -5
  22. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/cloud/google.py +7 -4
  23. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/notebooks/jupyter.py +39 -28
  24. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/data/__init__.py +23 -7
  25. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/data/snowflake/__init__.py +8 -3
  26. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/__init__.py +11 -7
  27. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/csv/__init__.py +23 -4
  28. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/feather/__init__.py +27 -7
  29. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/parquet/__init__.py +28 -6
  30. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/pdf/__init__.py +5 -5
  31. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/pptx/__init__.py +2 -2
  32. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/pptx/markdown.py +2 -2
  33. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/sheets/__init__.py +12 -7
  34. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/slides/__init__.py +8 -8
  35. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/tex/__init__.py +3 -3
  36. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/xlsx/__init__.py +49 -16
  37. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/__init__.py +94 -24
  38. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/analytics/attribution.py +3 -3
  39. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/colours.py +90 -14
  40. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/__init__.py +17 -18
  41. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/backends.py +17 -5
  42. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pandas/__init__.py +2 -0
  43. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pandas/dataframes.py +103 -12
  44. mayutils-3.0.0/src/mayutils/objects/dataframes/polars/__init__.py +34 -0
  45. mayutils-3.0.0/src/mayutils/objects/dataframes/polars/dataframes.py +126 -0
  46. mayutils-3.0.0/src/mayutils/objects/dataframes/temporal.py +246 -0
  47. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/hashing.py +5 -4
  48. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/types.py +7 -4
  49. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/scripts/generate_plotly_stubs.py +3 -1
  50. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/scripts/refresh_stubs.py +15 -11
  51. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/console.py +68 -7
  52. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/combine.py +4 -4
  53. mayutils-3.0.0/src/mayutils/visualisation/graphs/plotly/__init__.py +148 -0
  54. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/charts/__init__.py +71 -14
  55. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/charts/plot.py +4 -2
  56. mayutils-3.0.0/src/mayutils/visualisation/graphs/plotly/traces/__init__.py +91 -0
  57. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/null.py +2 -1
  58. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/utilities.py +5 -2
  59. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/utilities.py +10 -3
  60. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/notebook.py +7 -7
  61. mayutils-2.0.0/src/mayutils/objects/dataframes/polars/__init__.py +0 -30
  62. mayutils-2.0.0/src/mayutils/objects/dataframes/polars/dataframes.py +0 -26
  63. mayutils-2.0.0/src/mayutils/visualisation/graphs/plotly/__init__.py +0 -69
  64. mayutils-2.0.0/src/mayutils/visualisation/graphs/plotly/traces/__init__.py +0 -25
  65. {mayutils-2.0.0 → mayutils-3.0.0}/LICENSE +0 -0
  66. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/core/__init__.py +0 -0
  67. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/core/constants.py +0 -0
  68. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/core/extras.py +0 -0
  69. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/data/__init__.py +0 -0
  70. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/data/cache/.gitkeep +0 -0
  71. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/__init__.py +0 -0
  72. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/benchmarking.py +0 -0
  73. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/filesystem/__init__.py +0 -0
  74. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/filesystem/encoding.py +0 -0
  75. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/filesystem/reading.py +0 -0
  76. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/__init__.py +0 -0
  77. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/decorators.py +0 -0
  78. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/decorators.pyi +0 -0
  79. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/environment/memoisation/memory.py +0 -0
  80. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/export/__init__.py +0 -0
  81. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/export/html.py +0 -0
  82. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/export/images.py +0 -0
  83. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/__init__.py +0 -0
  84. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/cloud/__init__.py +0 -0
  85. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/__init__.py +0 -0
  86. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/notebooks/__init__.py +0 -0
  87. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/tui/__init__.py +0 -0
  88. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/tui/textual.py +0 -0
  89. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/code/tui/tuiplot.py +0 -0
  90. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/docs/__init__.py +0 -0
  91. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/docx/__init__.py +0 -0
  92. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/markdown/__init__.py +0 -0
  93. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/filetypes/pptx/units.py +0 -0
  94. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/__init__.py +0 -0
  95. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/css/default.css +0 -0
  96. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/images/.gitkeep +0 -0
  97. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/views/__init__.py +0 -0
  98. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/views/forbidden.py +0 -0
  99. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/interfaces/websites/streamlit/views/login.py +0 -0
  100. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/__init__.py +0 -0
  101. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/analytics/__init__.py +0 -0
  102. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/machine_learning/__init__.py +0 -0
  103. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/numba.py +0 -0
  104. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/numpy.py +0 -0
  105. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/mathematics/statistics/__init__.py +0 -0
  106. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/__init__.py +0 -0
  107. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/classes.py +0 -0
  108. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/dask/__init__.py +0 -0
  109. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/modin/__init__.py +0 -0
  110. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pandas/index.py +0 -0
  111. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pandas/series.py +0 -0
  112. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pandas/stylers.py +0 -0
  113. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/pyarrow/__init__.py +0 -0
  114. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dataframes/snowflake/__init__.py +0 -0
  115. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/__init__.py +0 -0
  116. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/constants.py +0 -0
  117. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/datetime.py +0 -0
  118. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/interval.py +0 -0
  119. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/timezone.py +0 -0
  120. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/datetime/traveller.py +0 -0
  121. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/decorators.py +0 -0
  122. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/dictionaries.py +0 -0
  123. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/functions.py +0 -0
  124. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/numbers.py +0 -0
  125. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/paths.py +0 -0
  126. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/strings.py +0 -0
  127. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/objects/versions.py +0 -0
  128. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/py.typed +0 -0
  129. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/scripts/__init__.py +0 -0
  130. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/scripts/clear_cache.py +0 -0
  131. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/testing/__init__.py +0 -0
  132. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/__init__.py +0 -0
  133. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/__init__.py +0 -0
  134. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/matplotlib/__init__.py +0 -0
  135. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/matplotlib/templates.py +0 -0
  136. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/charts/plot.pyi +0 -0
  137. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/charts/subplot.py +0 -0
  138. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/charts/subplot.pyi +0 -0
  139. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/templates.py +0 -0
  140. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/ecdf.py +0 -0
  141. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/ecdf.pyi +0 -0
  142. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/icicle.py +0 -0
  143. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/icicle.pyi +0 -0
  144. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/kde.py +0 -0
  145. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/kde.pyi +0 -0
  146. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/line.py +0 -0
  147. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/line.pyi +0 -0
  148. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/mesh3d.py +0 -0
  149. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/mesh3d.pyi +0 -0
  150. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/null.pyi +0 -0
  151. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/scatter.py +0 -0
  152. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/scatter.pyi +0 -0
  153. {mayutils-2.0.0 → mayutils-3.0.0}/src/mayutils/visualisation/graphs/plotly/traces/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mayutils
3
- Version: 2.0.0
3
+ Version: 3.0.0
4
4
  Summary: Utilities for Python from Mayuran Visakan
5
5
  Author: Mayuran Visakan
6
6
  Author-email: Mayuran Visakan <mayuran.k.v@gmail.com>
@@ -25,6 +25,7 @@ License: MIT License
25
25
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
26
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
27
  SOFTWARE.
28
+ Requires-Dist: jinja2>=3.1
28
29
  Requires-Dist: pydantic>=2.11.7
29
30
  Requires-Dist: pydantic-settings>=2.10.1
30
31
  Requires-Dist: mayutils[async,cli,console,dataframes,datetime,filesystem,financials,google,mathematics,microsoft,notebook,numerics,pandas,pdf,plotting,secrets,snowflake,sql,statistics,streamlit,tui,web] ; extra == 'all'
@@ -150,7 +151,7 @@ Description-Content-Type: text/markdown
150
151
 
151
152
  # mayutils
152
153
 
153
- [![CI](https://github.com/mayuran-visakan/mayutils/actions/workflows/ci.yaml/badge.svg)](https://github.com/mayuran-visakan/mayutils/actions/workflows/ci.yaml) [![Documentation](https://img.shields.io/badge/Documentation-mkdocs%20material-indigo.svg)](https://mayuran-visakan.github.io/mayutils/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
154
+ [![CI](https://img.shields.io/github/actions/workflow/status/mayurankv/mayutils/ci.yaml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white&label=CI)](https://github.com/mayurankv/mayutils/actions/workflows/ci.yaml) [![PyPI](https://img.shields.io/pypi/v/mayutils?style=for-the-badge&logo=pypi&logoColor=white&label=PyPI&color=3775A9)](https://pypi.org/project/mayutils/) [![Python](https://img.shields.io/badge/python-3.13%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/) [![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge&logo=opensourceinitiative&logoColor=white)](./LICENSE) [![Docs](https://img.shields.io/badge/docs-mkdocs--material-3F51B5?style=for-the-badge&logo=materialformkdocs&logoColor=white)](https://mayurankv.github.io/mayutils/)
154
155
 
155
156
  Utilities for Python — plotting helpers, dataframe adapters, Snowflake/SQL glue, PowerPoint/PDF export, notebook display tweaks, OAuth helpers, and a fistful of miscellaneous object helpers. Heavy dependencies are grouped behind extras so the core install stays small.
156
157
 
@@ -206,7 +207,7 @@ Full details: [docs/guides/dependency-groups.md](docs/guides/dependency-groups.m
206
207
  - [Documentation](docs/guides/documentation.md)
207
208
  - [Roadmap](docs/roadmap.md) — translated from the legacy `.todo` file
208
209
  - [Changelog](docs/changelog.md)
209
- - [API Reference](https://mayuran-visakan.github.io/mayutils/reference/) — auto-generated from docstrings
210
+ - [API Reference](https://mayurankv.github.io/mayutils/reference/) — auto-generated from docstrings
210
211
 
211
212
  ## Contributing
212
213
 
@@ -1,6 +1,6 @@
1
1
  # mayutils
2
2
 
3
- [![CI](https://github.com/mayuran-visakan/mayutils/actions/workflows/ci.yaml/badge.svg)](https://github.com/mayuran-visakan/mayutils/actions/workflows/ci.yaml) [![Documentation](https://img.shields.io/badge/Documentation-mkdocs%20material-indigo.svg)](https://mayuran-visakan.github.io/mayutils/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/mayurankv/mayutils/ci.yaml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white&label=CI)](https://github.com/mayurankv/mayutils/actions/workflows/ci.yaml) [![PyPI](https://img.shields.io/pypi/v/mayutils?style=for-the-badge&logo=pypi&logoColor=white&label=PyPI&color=3775A9)](https://pypi.org/project/mayutils/) [![Python](https://img.shields.io/badge/python-3.13%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/) [![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge&logo=opensourceinitiative&logoColor=white)](./LICENSE) [![Docs](https://img.shields.io/badge/docs-mkdocs--material-3F51B5?style=for-the-badge&logo=materialformkdocs&logoColor=white)](https://mayurankv.github.io/mayutils/)
4
4
 
5
5
  Utilities for Python — plotting helpers, dataframe adapters, Snowflake/SQL glue, PowerPoint/PDF export, notebook display tweaks, OAuth helpers, and a fistful of miscellaneous object helpers. Heavy dependencies are grouped behind extras so the core install stays small.
6
6
 
@@ -56,7 +56,7 @@ Full details: [docs/guides/dependency-groups.md](docs/guides/dependency-groups.m
56
56
  - [Documentation](docs/guides/documentation.md)
57
57
  - [Roadmap](docs/roadmap.md) — translated from the legacy `.todo` file
58
58
  - [Changelog](docs/changelog.md)
59
- - [API Reference](https://mayuran-visakan.github.io/mayutils/reference/) — auto-generated from docstrings
59
+ - [API Reference](https://mayurankv.github.io/mayutils/reference/) — auto-generated from docstrings
60
60
 
61
61
  ## Contributing
62
62
 
@@ -36,6 +36,7 @@ testing = ["pytest>=8.4.2"]
36
36
  [project]
37
37
  authors = [{email = "mayuran.k.v@gmail.com", name = "Mayuran Visakan"}]
38
38
  dependencies = [
39
+ "jinja2>=3.1",
39
40
  "pydantic>=2.11.7",
40
41
  "pydantic-settings>=2.10.1"
41
42
  ]
@@ -45,7 +46,7 @@ maintainers = [{email = "mayuran.k.v@gmail.com", name = "Mayuran Visakan"}]
45
46
  name = "mayutils"
46
47
  readme = "README.md"
47
48
  requires-python = ">=3.13,<4.0"
48
- version = "2.0.0"
49
+ version = "3.0.0"
49
50
 
50
51
  [project.optional-dependencies]
51
52
  all = [
@@ -208,7 +209,7 @@ pre_bump_hooks = [
208
209
  ]
209
210
  tag_format = "v$version"
210
211
  update_changelog_on_bump = true
211
- version = "2.0.0"
212
+ version = "3.0.0"
212
213
  version_files = ["pyproject.toml:^version"]
213
214
 
214
215
  [tool.mdformat]
@@ -44,15 +44,15 @@ def setup(
44
44
  >>> from mayutils import setup
45
45
  >>> setup(logging=False, plotly=False, notebook=False, pandas=False)
46
46
  """
47
- from mayutils.core.extras import format_missing_extra_hint # noqa: PLC0415
48
- from mayutils.environment.logging import Logger # noqa: PLC0415
47
+ from mayutils.core.extras import format_missing_extra_hint
48
+ from mayutils.environment.logging import Logger
49
49
 
50
50
  if logging:
51
51
  Logger.configure()
52
52
 
53
53
  if plotly:
54
54
  try:
55
- from mayutils.visualisation.graphs.plotly.templates import register_templates # noqa: PLC0415
55
+ from mayutils.visualisation.graphs.plotly.templates import register_templates
56
56
 
57
57
  register_templates()
58
58
  except ImportError as err:
@@ -63,7 +63,7 @@ def setup(
63
63
 
64
64
  if notebook:
65
65
  try:
66
- from mayutils.visualisation.notebook import Notebook # noqa: PLC0415
66
+ from mayutils.visualisation.notebook import Notebook
67
67
 
68
68
  Notebook.setup()
69
69
  except ImportError as err:
@@ -76,7 +76,7 @@ def setup(
76
76
 
77
77
  if pandas:
78
78
  try:
79
- from mayutils.objects.dataframes import setup_pandas # noqa: PLC0415
79
+ from mayutils.objects.dataframes import setup_pandas
80
80
 
81
81
  setup_pandas()
82
82
  except ImportError as err:
@@ -4,22 +4,20 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING, Any, Self, cast
6
6
 
7
- from mayutils.core.extras import may_require_extras
8
7
  from mayutils.data.queries import QUERIES_FOLDERS
9
8
  from mayutils.data.read import render_query
10
9
  from mayutils.environment.logging import Logger
11
10
  from mayutils.objects.dataframes.backends import Backend, BackendOperations, DataFrames, default_backend
12
- from mayutils.objects.datetime import DateTime, Interval
13
-
14
- with may_require_extras():
15
- import pandas as pd
16
11
 
17
12
  if TYPE_CHECKING:
13
+ from collections.abc import Mapping
18
14
  from pathlib import Path
19
15
 
16
+ import pandas as pd
17
+
20
18
  from mayutils.data.read import QueryReader
21
- from mayutils.objects.datetime import Duration
22
- from mayutils.objects.types import SQL, SupportsStr
19
+ from mayutils.objects.datetime import DateTime, Duration, Interval
20
+ from mayutils.objects.types import SQL
23
21
 
24
22
 
25
23
  logger = Logger.spawn()
@@ -29,14 +27,19 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
29
27
  """
30
28
  Incrementally pull rows by tracking ``max(cursor_column)``.
31
29
 
32
- The query template must contain a ``{cursor}`` placeholder. On each
33
- update the cursor is formatted into the template and only rows past
34
- the previous cursor are fetched.
30
+ The query template must contain a ``{{ cursor }}`` placeholder. On
31
+ each update the cursor is rendered into the template and only rows
32
+ past the previous cursor are fetched. Results are returned exactly
33
+ as the *reader* produces them: unlike
34
+ :func:`mayutils.data.read.read_query`, no automatic temporal column
35
+ parsing is applied, keeping the schema stable across incremental
36
+ fetches that are concatenated together.
35
37
 
36
38
  Parameters
37
39
  ----------
38
40
  query
39
- SQL string or path to a ``.sql`` template containing ``{cursor}``.
41
+ SQL string or path to a ``.sql`` template containing
42
+ ``{{ cursor }}``.
40
43
  cursor_column
41
44
  Column whose maximum is tracked as the cursor.
42
45
  initial_cursor
@@ -55,9 +58,10 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
55
58
  :meth:`~datetime.datetime.strftime` format for datetime cursors.
56
59
  queries_folders
57
60
  Directories searched when *query* is a filename.
58
- **fixed_format_kwargs
59
- Extra keyword arguments substituted into the query template on
60
- every call.
61
+ template_kwargs
62
+ Jinja2 template variables rendered into the query template on
63
+ every call. The key ``cursor`` is injected per-call by
64
+ :meth:`fetch` and overrides any value stored here.
61
65
 
62
66
  See Also
63
67
  --------
@@ -82,7 +86,7 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
82
86
  update_frequency: Duration | None = None,
83
87
  time_format: str = "%Y-%m-%d %H:%M:%S",
84
88
  queries_folders: tuple[Path, ...] = QUERIES_FOLDERS,
85
- **fixed_format_kwargs: SupportsStr,
89
+ template_kwargs: Mapping[str, object] | None = None,
86
90
  ) -> None:
87
91
  """
88
92
  Initialise the streaming query and perform the first fetch.
@@ -94,7 +98,7 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
94
98
  ----------
95
99
  query
96
100
  SQL string or path to a ``.sql`` template containing
97
- ``{cursor}``.
101
+ ``{{ cursor }}``.
98
102
  cursor_column
99
103
  Column whose maximum is tracked as the cursor.
100
104
  initial_cursor
@@ -115,9 +119,10 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
115
119
  cursors.
116
120
  queries_folders
117
121
  Directories searched when *query* is a filename.
118
- **fixed_format_kwargs
119
- Extra keyword arguments substituted into the query template
120
- on every call.
122
+ template_kwargs
123
+ Jinja2 template variables rendered into the query template
124
+ on every call. The key ``cursor`` is injected per-call by
125
+ :meth:`fetch` and overrides any value stored here.
121
126
 
122
127
  See Also
123
128
  --------
@@ -127,7 +132,7 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
127
132
  --------
128
133
  >>> from mayutils.data.live import StreamingQuery # doctest: +SKIP
129
134
  >>> sq = StreamingQuery( # doctest: +SKIP
130
- ... "SELECT * FROM t WHERE id > {cursor}",
135
+ ... "SELECT * FROM t WHERE id > {{ cursor }}",
131
136
  ... cursor_column="id",
132
137
  ... initial_cursor=0,
133
138
  ... reader=my_reader,
@@ -142,11 +147,13 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
142
147
  self.update_frequency = update_frequency
143
148
  self.time_format = time_format
144
149
  self.queries_folders = queries_folders
145
- self.fixed_format_kwargs = fixed_format_kwargs
150
+ self.template_kwargs: dict[str, object] = dict(template_kwargs or {})
146
151
  self.initial_cursor = initial_cursor
147
152
 
148
153
  self.validate_retention()
149
154
 
155
+ from mayutils.objects.datetime import DateTime
156
+
150
157
  self.cursor_value: Any = initial_cursor
151
158
  self.cursor_is_datetime: bool = hasattr(initial_cursor, "strftime")
152
159
  self._data: DataFrameType = self.fetch()
@@ -208,6 +215,8 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
208
215
  if not self.should_update(force=force):
209
216
  return self
210
217
 
218
+ from mayutils.objects.datetime import DateTime
219
+
211
220
  snapshot = self.data
212
221
  try:
213
222
  delta = self.fetch()
@@ -248,6 +257,8 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
248
257
  --------
249
258
  >>> sq.reset() # doctest: +SKIP
250
259
  """
260
+ from mayutils.objects.datetime import DateTime
261
+
251
262
  self._data = self.fetch()
252
263
  self.last_updated = DateTime.now()
253
264
 
@@ -274,7 +285,7 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
274
285
  --------
275
286
  >>> delta = sq.fetch() # doctest: +SKIP
276
287
  """
277
- data = self.read_query(cursor=self.cursor)
288
+ data = self.read_query(template_kwargs={"cursor": self.cursor})
278
289
 
279
290
  if len(data) != 0:
280
291
  self.cursor_value = BackendOperations.max(data, self.cursor_column, backend=self.backend)
@@ -313,21 +324,23 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
313
324
  self,
314
325
  *,
315
326
  default_suffix: str = "sql",
316
- **extra_kwargs: SupportsStr,
327
+ template_kwargs: Mapping[str, object] | None = None,
317
328
  ) -> DataFrameType:
318
329
  """
319
330
  Render and execute the SQL query template.
320
331
 
321
- Combines *fixed_format_kwargs* with any additional keyword
322
- arguments, renders the template, and passes it to *reader*.
332
+ Merges the per-call *template_kwargs* over the mapping stored at
333
+ construction (per-call keys win), renders the template, and
334
+ passes it to *reader*.
323
335
 
324
336
  Parameters
325
337
  ----------
326
338
  default_suffix
327
339
  File extension appended when *query* is a filename without
328
340
  one.
329
- **extra_kwargs
330
- Additional format arguments merged into the template.
341
+ template_kwargs
342
+ Per-call Jinja2 template variables merged over the stored
343
+ mapping.
331
344
 
332
345
  Returns
333
346
  -------
@@ -339,14 +352,13 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
339
352
 
340
353
  Examples
341
354
  --------
342
- >>> df = sq.read_query(cursor="0") # doctest: +SKIP
355
+ >>> df = sq.read_query(template_kwargs={"cursor": "0"}) # doctest: +SKIP
343
356
  """
344
357
  rendered = render_query(
345
358
  self.query,
346
359
  queries_folders=self.queries_folders,
347
360
  default_suffix=default_suffix,
348
- **self.fixed_format_kwargs,
349
- **extra_kwargs,
361
+ template_kwargs={**self.template_kwargs, **(template_kwargs or {})},
350
362
  )
351
363
 
352
364
  return self.reader(
@@ -387,6 +399,8 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
387
399
  if force or self.update_frequency is None:
388
400
  return True
389
401
 
402
+ from mayutils.objects.datetime import DateTime
403
+
390
404
  return (DateTime.now() - self.last_updated) > self.update_frequency
391
405
 
392
406
  def apply_retention(
@@ -409,6 +423,8 @@ class StreamingQuery[DataFrameType: DataFrames = pd.DataFrame]:
409
423
  if self.max_age is not None and len(self.data) > 0:
410
424
  newest = BackendOperations.max(self.data, self.cursor_column, backend=self.backend)
411
425
  if hasattr(newest, "strftime"):
426
+ from mayutils.objects.datetime import DateTime
427
+
412
428
  try:
413
429
  cutoff = DateTime.parse(cast("DateTime", newest).strftime(self.time_format)) - self.max_age
414
430
 
@@ -464,9 +480,13 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
464
480
  """
465
481
  Manage a sliding or expanding time window over a SQL query.
466
482
 
467
- The query template must contain ``{start_timestamp}`` and
468
- ``{end_timestamp}`` placeholders. On each update, only the delta
469
- since the previous window end is fetched.
483
+ The query template must contain ``{{ start_timestamp }}`` and
484
+ ``{{ end_timestamp }}`` placeholders. On each update, only the
485
+ delta since the previous window end is fetched. Results are
486
+ returned exactly as the *reader* produces them: unlike
487
+ :func:`mayutils.data.read.read_query`, no automatic temporal column
488
+ parsing is applied, keeping the schema stable across windowed
489
+ fetches that are concatenated together.
470
490
 
471
491
  When *deduplicate* is ``True``, rows are deduped on *index_column*
472
492
  after each concat (keeps last), which handles re-fetched open
@@ -476,7 +496,7 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
476
496
  ----------
477
497
  query
478
498
  SQL string or path to a ``.sql`` template containing
479
- ``{start_timestamp}`` and ``{end_timestamp}``.
499
+ ``{{ start_timestamp }}`` and ``{{ end_timestamp }}``.
480
500
  index_column
481
501
  Column used for deduplication and rolling-window filtering.
482
502
  start_timestamp
@@ -499,9 +519,11 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
499
519
  :meth:`~datetime.datetime.strftime` format for window boundaries.
500
520
  queries_folders
501
521
  Directories searched when *query* is a filename.
502
- **fixed_format_kwargs
503
- Extra keyword arguments substituted into the query template on
504
- every call.
522
+ template_kwargs
523
+ Jinja2 template variables rendered into the query template on
524
+ every call. The keys ``start_timestamp`` and ``end_timestamp``
525
+ are injected per-call by :meth:`fetch` and override any values
526
+ stored here.
505
527
 
506
528
  See Also
507
529
  --------
@@ -528,7 +550,7 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
528
550
  update_frequency: Duration | None = None,
529
551
  time_format: str = "%Y-%m-%d",
530
552
  queries_folders: tuple[Path, ...] = QUERIES_FOLDERS,
531
- **fixed_format_kwargs: SupportsStr,
553
+ template_kwargs: Mapping[str, object] | None = None,
532
554
  ) -> None:
533
555
  """
534
556
  Initialise the windowed query and perform the first fetch.
@@ -540,7 +562,7 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
540
562
  ----------
541
563
  query
542
564
  SQL string or path to a ``.sql`` template containing
543
- ``{start_timestamp}`` and ``{end_timestamp}``.
565
+ ``{{ start_timestamp }}`` and ``{{ end_timestamp }}``.
544
566
  index_column
545
567
  Column used for deduplication and rolling-window filtering.
546
568
  start_timestamp
@@ -565,9 +587,11 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
565
587
  boundaries.
566
588
  queries_folders
567
589
  Directories searched when *query* is a filename.
568
- **fixed_format_kwargs
569
- Extra keyword arguments substituted into the query template
570
- on every call.
590
+ template_kwargs
591
+ Jinja2 template variables rendered into the query template
592
+ on every call. The keys ``start_timestamp`` and
593
+ ``end_timestamp`` are injected per-call by :meth:`fetch`
594
+ and override any values stored here.
571
595
 
572
596
  See Also
573
597
  --------
@@ -577,7 +601,7 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
577
601
  --------
578
602
  >>> from mayutils.data.live import WindowedQuery # doctest: +SKIP
579
603
  >>> wq = WindowedQuery( # doctest: +SKIP
580
- ... "SELECT * FROM t WHERE ts BETWEEN '{start_timestamp}' AND '{end_timestamp}'",
604
+ ... "SELECT * FROM t WHERE ts BETWEEN '{{ start_timestamp }}' AND '{{ end_timestamp }}'",
581
605
  ... index_column="ts",
582
606
  ... start_timestamp=DateTime.now(),
583
607
  ... reader=my_reader,
@@ -594,11 +618,13 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
594
618
  self.update_frequency = update_frequency
595
619
  self.time_format = time_format
596
620
  self.queries_folders = queries_folders
597
- self.fixed_format_kwargs = fixed_format_kwargs
621
+ self.template_kwargs: dict[str, object] = dict(template_kwargs or {})
598
622
  self.start_timestamp = start_timestamp
599
623
 
600
624
  self.validate_retention()
601
625
 
626
+ from mayutils.objects.datetime import DateTime, Interval
627
+
602
628
  self._interval: Interval[DateTime] = Interval(
603
629
  start=start_timestamp,
604
630
  end=DateTime.now(),
@@ -738,6 +764,8 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
738
764
  if not self.should_update(force=force):
739
765
  return self
740
766
 
767
+ from mayutils.objects.datetime import DateTime
768
+
741
769
  snapshot = self.data
742
770
  snapshot_interval = self.interval
743
771
  try:
@@ -794,6 +822,8 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
794
822
  --------
795
823
  >>> wq.reset() # doctest: +SKIP
796
824
  """
825
+ from mayutils.objects.datetime import DateTime, Interval
826
+
797
827
  if start_timestamp is not None:
798
828
  self.start_timestamp = start_timestamp
799
829
 
@@ -840,10 +870,14 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
840
870
  """
841
871
  if initial:
842
872
  return self.read_query(
843
- start_timestamp=self._interval.start.strftime(format=self.time_format),
844
- end_timestamp=self._interval.end.strftime(format=self.time_format),
873
+ template_kwargs={
874
+ "start_timestamp": self._interval.start.strftime(format=self.time_format),
875
+ "end_timestamp": self._interval.end.strftime(format=self.time_format),
876
+ },
845
877
  )
846
878
 
879
+ from mayutils.objects.datetime import DateTime, Interval
880
+
847
881
  now = DateTime.now()
848
882
  previous_end = self._interval.end
849
883
 
@@ -858,8 +892,10 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
858
892
  )
859
893
 
860
894
  delta = self.read_query(
861
- start_timestamp=previous_end.strftime(format=self.time_format),
862
- end_timestamp=now.strftime(format=self.time_format),
895
+ template_kwargs={
896
+ "start_timestamp": previous_end.strftime(format=self.time_format),
897
+ "end_timestamp": now.strftime(format=self.time_format),
898
+ },
863
899
  )
864
900
 
865
901
  self._interval = Interval(start=new_start, end=now, absolute=True)
@@ -871,21 +907,23 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
871
907
  /,
872
908
  *,
873
909
  default_suffix: str = "sql",
874
- **extra_kwargs: SupportsStr,
910
+ template_kwargs: Mapping[str, object] | None = None,
875
911
  ) -> DataFrameType:
876
912
  """
877
913
  Render and execute the SQL query template.
878
914
 
879
- Combines *fixed_format_kwargs* with any additional keyword
880
- arguments, renders the template, and passes it to *reader*.
915
+ Merges the per-call *template_kwargs* over the mapping stored at
916
+ construction (per-call keys win), renders the template, and
917
+ passes it to *reader*.
881
918
 
882
919
  Parameters
883
920
  ----------
884
921
  default_suffix
885
922
  File extension appended when *query* is a filename without
886
923
  one.
887
- **extra_kwargs
888
- Additional format arguments merged into the template.
924
+ template_kwargs
925
+ Per-call Jinja2 template variables merged over the stored
926
+ mapping.
889
927
 
890
928
  Returns
891
929
  -------
@@ -898,16 +936,17 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
898
936
  Examples
899
937
  --------
900
938
  >>> df = wq.read_query( # doctest: +SKIP
901
- ... start_timestamp="2024-01-01",
902
- ... end_timestamp="2024-01-02",
939
+ ... template_kwargs={
940
+ ... "start_timestamp": "2024-01-01",
941
+ ... "end_timestamp": "2024-01-02",
942
+ ... },
903
943
  ... )
904
944
  """
905
945
  rendered = render_query(
906
946
  self.query,
907
947
  queries_folders=self.queries_folders,
908
948
  default_suffix=default_suffix,
909
- **self.fixed_format_kwargs,
910
- **extra_kwargs,
949
+ template_kwargs={**self.template_kwargs, **(template_kwargs or {})},
911
950
  )
912
951
 
913
952
  return self.reader(
@@ -948,6 +987,8 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
948
987
  if force or self.update_frequency is None:
949
988
  return True
950
989
 
990
+ from mayutils.objects.datetime import DateTime
991
+
951
992
  return (DateTime.now() - self.last_updated) > self.update_frequency
952
993
 
953
994
  def apply_retention(
@@ -970,6 +1011,8 @@ class WindowedQuery[DataFrameType: DataFrames = pd.DataFrame]:
970
1011
  if self.max_age is not None and len(self.data) > 0:
971
1012
  newest = BackendOperations.max(self.data, self.index_column, backend=self.backend)
972
1013
  if hasattr(newest, "strftime"):
1014
+ from mayutils.objects.datetime import DateTime
1015
+
973
1016
  try:
974
1017
  cutoff = DateTime.parse(cast("DateTime", newest).strftime(self.time_format)) - self.max_age
975
1018