time-split 0.6.0__tar.gz → 0.7.1__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 (40) hide show
  1. {time_split-0.6.0 → time_split-0.7.1}/PKG-INFO +30 -17
  2. {time_split-0.6.0 → time_split-0.7.1}/README.md +13 -7
  3. {time_split-0.6.0 → time_split-0.7.1}/pyproject.toml +56 -56
  4. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/__init__.py +2 -2
  5. time_split-0.7.1/src/time_split/__main__.py +9 -0
  6. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_limits.py +15 -7
  7. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_schedule.py +2 -2
  8. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_splitter.py +1 -23
  9. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_docstrings.py +2 -9
  10. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/_plot.py +15 -5
  11. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/_progress.py +2 -3
  12. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/_to_string.py +64 -26
  13. time_split-0.7.1/src/time_split/app/__init__.py +11 -0
  14. {time_split-0.6.0/src/time_split/support → time_split-0.7.1/src/time_split/app}/_create_explorer_link.py +16 -10
  15. time_split-0.7.1/src/time_split/app/reexport/__init__.py +58 -0
  16. time_split-0.7.1/src/time_split/cli.py +29 -0
  17. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/settings.py +30 -0
  18. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/support/__init__.py +3 -5
  19. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/types.py +3 -1
  20. time_split-0.6.0/src/time_split/_compat.py +0 -49
  21. {time_split-0.6.0 → time_split-0.7.1}/LICENSE.md +0 -0
  22. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/__init__.py +1 -1
  23. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_datetime_index_like.py +0 -0
  24. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_process_available.py +0 -0
  25. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_backend/_span.py +0 -0
  26. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/__init__.py +2 -2
  27. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/_split.py +0 -0
  28. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_frontend/_weight.py +0 -0
  29. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/_support.py +0 -0
  30. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/__init__.py +0 -0
  31. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/_log_progress.py +0 -0
  32. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/base.py +0 -0
  33. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/pandas/__init__.py +0 -0
  34. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/pandas/_impl.py +0 -0
  35. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/polars/__init__.py +0 -0
  36. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/polars/_impl.py +0 -0
  37. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/sklearn/__init__.py +0 -0
  38. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/integration/sklearn/_impl.py +0 -0
  39. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/py.typed +0 -0
  40. {time_split-0.6.0 → time_split-0.7.1}/src/time_split/support/types.py +0 -0
@@ -1,31 +1,38 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: time-split
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Time-based k-fold validation splits for heterogeneous data.
5
- Home-page: https://github.com/rsundqvist/time-split
5
+ License: MIT
6
+ Keywords: hyper,parameter,optimization,cross,validation,time,temporal,heterogenous,data
6
7
  Author: Richard Sundqvist
7
8
  Author-email: richard.sundqvist@live.se
8
9
  Requires-Python: >=3.11
9
- Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Development Status :: 4 - Beta
10
11
  Classifier: Intended Audience :: Developers
11
12
  Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Natural Language :: English
13
+ Classifier: Operating System :: POSIX :: Linux
14
14
  Classifier: Operating System :: MacOS
15
15
  Classifier: Operating System :: Microsoft :: Windows
16
- Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Typing :: Typed
17
19
  Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
18
21
  Classifier: Programming Language :: Python :: 3.11
19
22
  Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Provides-Extra: app
21
25
  Provides-Extra: plotting
26
+ Requires-Dist: click ; extra == "app"
22
27
  Requires-Dist: matplotlib ; extra == "plotting"
23
28
  Requires-Dist: pandas (>=2.0.3)
24
- Requires-Dist: rics (>=4.1.1)
29
+ Requires-Dist: rics (>=6.0.1)
25
30
  Requires-Dist: seaborn ; extra == "plotting"
31
+ Requires-Dist: time-split-app ; extra == "app"
26
32
  Project-URL: Bug Tracker, https://github.com/rsundqvist/time-split/issues
27
33
  Project-URL: Changelog, https://github.com/rsundqvist/time-split/blob/master/CHANGELOG.md
28
- Project-URL: Documentation, https://time-split.readthedocs.io
34
+ Project-URL: Documentation, https://time-split
35
+ Project-URL: Homepage, https://github.com/rsundqvist/time-split
29
36
  Project-URL: Repository, https://github.com/rsundqvist/time-split
30
37
  Description-Content-Type: text/markdown
31
38
 
@@ -61,28 +68,34 @@ strong non-stationary characteristics.
61
68
  schedule itself.
62
69
  - Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
63
70
  supported data types.
64
- - [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
71
+ - [Plotting function](https://time-split.readthedocs.io/en/stable/api/time_split.html#time_split.plot) for
65
72
  visualization of folds. Display fold sizes (hour/row count), or
66
73
  [use custom text](https://time-split.readthedocs.io/en/stable/auto_examples/plotting_with_metrics.html).
67
- - [Integrations](https://time-split.readthedocs.io/en/stable/generated/time_split.integration.html) for popular
74
+ - [Integrations](https://time-split.readthedocs.io/en/stable/api/time_split.integration.html) for popular
68
75
  libraries such as `pandas`, `polars` and `scikit-learn`.
69
76
  - Convenient [web application](#experimenting-with-parameters) for exploring folds with different parameters.
70
77
 
71
78
  ## Experimenting with parameters
72
- The **Time Fold Explorer** application
79
+ The **Time Split** application
73
80
  (available [here](https://time-split.streamlit.app/?data=1554942900-1557610200&schedule=0+0+%2A+%2A+MON%2CFRI&n_splits=2&step=2&show_removed=True))
74
81
  is designed to help evaluate the effects of different
75
82
  [parameters](https://time-split.readthedocs.io/en/stable/#parameter-overview).
76
- To start it locally using
77
- [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](https://hub.docker.com/r/rsundqvist/time-split/)
78
- Docker, run
83
+ To start it locally, run
79
84
  ```sh
80
85
  docker run -p 8501:8501 rsundqvist/time-split
81
86
  ```
87
+ or
88
+ ```bash
89
+ pip install time-split[app]
90
+ python -m time_split app start
91
+ ```
82
92
  in the terminal. You may use
83
- [`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/generated/time_split.support.html#time_split.support.create_explorer_link)
93
+ [`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/api/time_split.app.html#time_split.app.create_explorer_link)
84
94
  to build application URLs with preselected splitting parameters.
85
95
 
96
+ Click [here](https://time-split.readthedocs.io/en/stable/api/time_split.app.reexport.html) for documentation of the most
97
+ important types, functions and classes used by the application.
98
+
86
99
  ## Installation
87
100
  The package is published through the [Python Package Index (PyPI)]. Source code
88
101
  is available on GitHub: https://github.com/rsundqvist/time-split
@@ -30,28 +30,34 @@ strong non-stationary characteristics.
30
30
  schedule itself.
31
31
  - Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
32
32
  supported data types.
33
- - [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
33
+ - [Plotting function](https://time-split.readthedocs.io/en/stable/api/time_split.html#time_split.plot) for
34
34
  visualization of folds. Display fold sizes (hour/row count), or
35
35
  [use custom text](https://time-split.readthedocs.io/en/stable/auto_examples/plotting_with_metrics.html).
36
- - [Integrations](https://time-split.readthedocs.io/en/stable/generated/time_split.integration.html) for popular
36
+ - [Integrations](https://time-split.readthedocs.io/en/stable/api/time_split.integration.html) for popular
37
37
  libraries such as `pandas`, `polars` and `scikit-learn`.
38
38
  - Convenient [web application](#experimenting-with-parameters) for exploring folds with different parameters.
39
39
 
40
40
  ## Experimenting with parameters
41
- The **Time Fold Explorer** application
41
+ The **Time Split** application
42
42
  (available [here](https://time-split.streamlit.app/?data=1554942900-1557610200&schedule=0+0+%2A+%2A+MON%2CFRI&n_splits=2&step=2&show_removed=True))
43
43
  is designed to help evaluate the effects of different
44
44
  [parameters](https://time-split.readthedocs.io/en/stable/#parameter-overview).
45
- To start it locally using
46
- [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](https://hub.docker.com/r/rsundqvist/time-split/)
47
- Docker, run
45
+ To start it locally, run
48
46
  ```sh
49
47
  docker run -p 8501:8501 rsundqvist/time-split
50
48
  ```
49
+ or
50
+ ```bash
51
+ pip install time-split[app]
52
+ python -m time_split app start
53
+ ```
51
54
  in the terminal. You may use
52
- [`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/generated/time_split.support.html#time_split.support.create_explorer_link)
55
+ [`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/api/time_split.app.html#time_split.app.create_explorer_link)
53
56
  to build application URLs with preselected splitting parameters.
54
57
 
58
+ Click [here](https://time-split.readthedocs.io/en/stable/api/time_split.app.reexport.html) for documentation of the most
59
+ important types, functions and classes used by the application.
60
+
55
61
  ## Installation
56
62
  The package is published through the [Python Package Index (PyPI)]. Source code
57
63
  is available on GitHub: https://github.com/rsundqvist/time-split
@@ -1,15 +1,15 @@
1
- [tool.poetry]
1
+ [project]
2
2
  name = "time-split"
3
- version = "0.6.0"
3
+ version = "0.7.1"
4
4
  description = "Time-based k-fold validation splits for heterogeneous data."
5
- authors = ["Richard Sundqvist <richard.sundqvist@live.se>"]
6
-
5
+ authors = [
6
+ { name = "Richard Sundqvist", email = "richard.sundqvist@live.se" },
7
+ ]
8
+ license = { text = "MIT" }
7
9
  readme = "README.md"
8
- homepage = "https://github.com/rsundqvist/time-split"
9
- repository = "https://github.com/rsundqvist/time-split"
10
- documentation = "https://time-split.readthedocs.io"
10
+ keywords = ["hyper", "parameter", "optimization", "cross", "validation", "time", "temporal", "heterogenous", "data"]
11
11
  classifiers = [
12
- "Development Status :: 3 - Alpha",
12
+ "Development Status :: 4 - Beta",
13
13
  "Intended Audience :: Developers",
14
14
  "Intended Audience :: Science/Research",
15
15
  "Operating System :: POSIX :: Linux",
@@ -17,50 +17,54 @@ classifiers = [
17
17
  "Operating System :: Microsoft :: Windows",
18
18
  "License :: OSI Approved :: MIT License",
19
19
  "Natural Language :: English",
20
+ "Typing :: Typed",
20
21
  "Programming Language :: Python :: 3",
21
22
  "Programming Language :: Python :: 3 :: Only",
22
23
  "Programming Language :: Python :: 3.11",
23
24
  "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
24
26
  ]
27
+ requires-python = ">=3.11"
25
28
 
29
+ dependencies = [
30
+ "pandas >= 2.0.3",
31
+ "rics >= 6.0.1",
32
+ ]
26
33
 
27
- [tool.poetry.urls]
34
+ [project.optional-dependencies]
35
+ app = ["click", "time-split-app"] # Constraints managed by app.
36
+ plotting = ["matplotlib", "seaborn"]
37
+
38
+ [project.urls]
39
+ homepage = "https://github.com/rsundqvist/time-split"
40
+ repository = "https://github.com/rsundqvist/time-split"
41
+ documentation = "https://time-split"
28
42
  "Bug Tracker" = "https://github.com/rsundqvist/time-split/issues"
29
43
  "Changelog" = "https://github.com/rsundqvist/time-split/blob/master/CHANGELOG.md"
30
44
 
31
- [tool.poetry.scripts]
45
+ [project.entry-points."rics.cli"]
46
+ time-split = "time_split.cli:main"
32
47
 
33
- [tool.poetry.dependencies]
34
- python = ">=3.11"
35
-
36
- pandas = ">=2.0.3"
37
- rics = ">=4.1.1"
38
-
39
- # plotting
40
- matplotlib = { version = "*", optional = true }
41
- seaborn = { version = "*", optional = true }
42
-
43
- [tool.poetry.extras]
44
- plotting = ["matplotlib", "seaborn"]
48
+ [tool.poetry.requires-plugins]
49
+ poetry-plugin-export = ">=1.8"
45
50
 
46
51
  [tool.poetry.group.manual-extras.dependencies]
47
52
  # Extras that must be available for docs + tests go here. Doc/test-only deps go in their respective groups.
48
- croniter = "^2.0.3"
49
- pyarrow = ">=15.0.2,<17.0.0"
50
- polars = { version = "^0.20.19", extras = ["pandas"] }
51
- streamlit = "*"
53
+ croniter = "^6.0.0"
54
+ pyarrow = "^19.0.0"
55
+ polars = { version = "^1.21.0", extras = ["pandas"] }
52
56
 
53
57
  [tool.poetry.group.docs]
54
58
  [tool.poetry.group.docs.dependencies]
55
59
  # duplicate object warnings for sphinx>=4
56
60
  # description: https://github.com/astropy/astropy/issues/11723
57
61
  # https://github.com/astropy/astropy/pull/12270
58
- sphinx = "^7.2.6"
59
- sphinx-gallery = "^0.16.0"
60
- pydata-sphinx-theme = "^0.15.2"
61
- myst-parser = ">=2,<4"
62
- nbsphinx = "^0.9.3"
63
- ipython = "^8.23.0"
62
+ sphinx = "~8.1.3"
63
+ sphinx-gallery = "^0.18.0"
64
+ pydata-sphinx-theme = "^0.16.1"
65
+ myst-parser = "^4.0.0"
66
+ nbsphinx = "^0.9.6"
67
+ ipython = "^8.31.0"
64
68
 
65
69
  [tool.poetry.group.notebooks]
66
70
  optional = true
@@ -71,26 +75,22 @@ jupyterlab-execute-time = "*"
71
75
  tqdm = "*"
72
76
 
73
77
  [tool.poetry.group.test.dependencies]
74
- pytest = "^8.1.1"
75
- xdoctest = "^1.1.3"
76
- coverage = "^7.4.4"
77
- pytest-cov = "^5.0.0"
78
-
79
- scikit-learn = "^1.4.2"
80
- # TODO Unpin.
81
- # dask.datasets.timeseries() warns:
82
- # UserWarning: dask_expr does not support the DataFrameIOFunction protocol for column projection
83
- # on dask == 2024.3.1
84
- dask = { version = "==2024.5.0", extras = ["dataframe"] }
78
+ pytest = "^8.3.4"
79
+ xdoctest = "^1.2.0"
80
+ coverage = "^7.6.10"
81
+ pytest-cov = "^6.0.0"
82
+
83
+ scikit-learn = "^1.6.1"
84
+ dask = { version = "^2025.1.0", extras = ["dataframe"] }
85
85
 
86
86
  [tool.poetry.group.devops.dependencies]
87
- invoke = "^2.2.0"
88
- mypy = "^1.10.1"
89
- ruff = "^0.5.0"
87
+ invoke = "~2.2.0"
88
+ mypy = "~1.17.0"
89
+ ruff = "~0.12.3"
90
90
 
91
- safety = "^3.2.3"
92
- codespell = "^2.3.0"
93
- bump2version = "^1.0.1"
91
+ safety = "~3.5.1"
92
+ codespell = "~2.4.1"
93
+ bump2version = "~1.0.1"
94
94
 
95
95
  [tool.ruff]
96
96
  line-length = 120
@@ -135,8 +135,6 @@ select = [
135
135
  "TRY",
136
136
  ]
137
137
  ignore = [
138
- "ANN101", # Missing type annotation for `self` in method
139
- "ANN102", # Missing type annotation for `cls` in method
140
138
  "D105", # Missing docstring in magic method
141
139
  "D107", # Missing docstring in `__init__`
142
140
  "ANN401",
@@ -147,8 +145,9 @@ ignore = [
147
145
  "PLR0913", #
148
146
  "SIM108", # Use ternary operator - coverage issue, see https://github.com/nedbat/coveragepy/issues/509
149
147
  "TD003", # Missing issue link
148
+ "TC006", # Add quotes to type expression in `typing.cast()`
149
+ "UP038", # https://github.com/astral-sh/ruff/issues/7871
150
150
  ]
151
- allowed-confusables = ["ℹ"]
152
151
 
153
152
  [tool.ruff.lint.per-file-ignores]
154
153
  "tests/*" = [
@@ -169,6 +168,10 @@ allowed-confusables = ["ℹ"]
169
168
  "ARG002", # Unused method argument: `groups`
170
169
  ]
171
170
 
171
+ "**/time_split/app/*" = [
172
+ "S101" # Allow 'assert' keyword
173
+ ]
174
+
172
175
  [tool.ruff.lint.pydocstyle]
173
176
  convention = "google"
174
177
  [tool.ruff.lint.pycodestyle]
@@ -179,13 +182,10 @@ max-line-length = 150
179
182
  filterwarnings = [
180
183
  "error",
181
184
  "ignore:Matplotlib is currently using agg:UserWarning",
182
- "ignore:.*utcfromtimestamp.*:DeprecationWarning", # Detta är farligt - ta bort
185
+ "ignore:.*'mode' parameter.*Pillow 13.*:DeprecationWarning",
183
186
  ]
184
187
  log_file_level = 10
185
188
  log_file = "pytest.log"
186
- norecursedirs = [
187
- "**/cli_modules/*",
188
- ]
189
189
 
190
190
  [build-system]
191
191
  requires = ["poetry-core>=1.0.8"]
@@ -5,12 +5,12 @@ import logging as _logging
5
5
  from ._frontend import log_split_progress, plot, split
6
6
 
7
7
  __all__ = [
8
+ "__version__", # Make MyPy happy
8
9
  "log_split_progress",
9
10
  "plot",
10
11
  "split",
11
- "__version__", # Make MyPy happy
12
12
  ]
13
13
 
14
- __version__ = "0.6.0"
14
+ __version__ = "0.7.1"
15
15
 
16
16
  _logging.getLogger(__name__).addHandler(_logging.NullHandler())
@@ -0,0 +1,9 @@
1
+ """Alias of :mod:`time_split.cli`.
2
+
3
+ As per, https://docs.python.org/3.13/library/__main__.html#id1,
4
+ this file doesn't use a ``if __name__ == "__main__"``-block.
5
+ """
6
+
7
+ from .cli import main
8
+
9
+ main()
@@ -4,7 +4,7 @@ from typing import NamedTuple
4
4
 
5
5
  from pandas import Timedelta, Timestamp
6
6
 
7
- from ..settings import auto_expand_limits
7
+ from ..settings import auto_expand_limits, misc
8
8
  from ..types import ExpandLimits, TimedeltaTypes
9
9
 
10
10
  LimitsTuple = tuple[Timestamp, Timestamp]
@@ -23,6 +23,8 @@ def expand_limits(
23
23
  ) -> LimitsTuple:
24
24
  """Derive the `"real"` bounds of `limits`.
25
25
 
26
+ Use :attr:`time_split.settings.misc.round_limits` to allow inward "expansion".
27
+
26
28
  Args:
27
29
  limits: A tuple ``(min, max)`` of timestamps.
28
30
  spec: Expansion spec as described in the :ref:`User guide`. Also supports level-tuples
@@ -61,6 +63,7 @@ def expand_limits(
61
63
  >>> expand_limits(limits, "d<14h")
62
64
  (Timestamp('2019-05-11 00:00:00'), Timestamp('2019-05-11 11:05:30'))
63
65
 
66
+ To disable this restriction, set :attr:`round_limits=True <.misc.round_limits>` in the settings.
64
67
  """
65
68
  if limits[0] >= limits[1]:
66
69
  msg = f"Bad limits. Expected limits[1] > limits[0], but got {limits=}."
@@ -102,19 +105,24 @@ def _from_levels(limits: LimitsTuple, *, levels: Iterable[_TimedeltaTuple] | Non
102
105
  def _apply(limits: LimitsTuple, *, level: _TimedeltaTuple) -> LimitsTuple:
103
106
  lo, hi = limits
104
107
 
105
- lo_floor = lo.floor(level.round_to)
108
+ round = misc.round_limits
109
+ lo_floor = lo.round(level.round_to) if round else lo.floor(level.round_to)
106
110
  if abs(lo_floor - lo) > level.tolerance:
107
111
  return limits
108
112
 
109
- hi_ceil = hi.ceil(level.round_to)
113
+ hi_ceil = hi.round(level.round_to) if round else hi.ceil(level.round_to)
110
114
  if abs(hi_ceil - hi) > level.tolerance:
111
115
  return limits
112
116
 
113
117
  if auto_expand_limits.SANITY_CHECK:
114
- if lo.round(level.round_to) != lo_floor:
115
- return limits
116
- if hi.round(level.round_to) != hi_ceil:
117
- return limits
118
+ if round:
119
+ if lo_floor == hi_ceil:
120
+ return limits
121
+ else:
122
+ if lo.round(level.round_to) != lo_floor:
123
+ return limits
124
+ if hi.round(level.round_to) != hi_ceil:
125
+ return limits
118
126
 
119
127
  return lo_floor, hi_ceil
120
128
 
@@ -1,4 +1,4 @@
1
- from typing import NamedTuple, cast, get_args
1
+ from typing import NamedTuple, get_args
2
2
 
3
3
  from pandas import DatetimeIndex, NaT, Timedelta, Timestamp, date_range
4
4
 
@@ -6,7 +6,7 @@ from ..types import DatetimeIterable, ExpandLimits, Schedule, TimedeltaTypes
6
6
  from ._limits import LimitsTuple
7
7
  from ._process_available import ProcessAvailableResult, process_available
8
8
 
9
- NO_LIMITS = cast(LimitsTuple, (NaT, NaT))
9
+ NO_LIMITS: LimitsTuple = NaT, NaT
10
10
 
11
11
 
12
12
  class MaterializedSchedule(NamedTuple):
@@ -1,6 +1,4 @@
1
- import logging
2
1
  from dataclasses import asdict, dataclass
3
- from functools import lru_cache
4
2
  from typing import cast, get_args
5
3
 
6
4
  from pandas import Timedelta
@@ -17,12 +15,9 @@ from ..types import (
17
15
  Span,
18
16
  TimedeltaTypes,
19
17
  )
20
- from ._limits import LimitsTuple
21
18
  from ._schedule import MaterializedSchedule, materialize_schedule
22
19
  from ._span import OffsetCalculator, to_strict_span
23
20
 
24
- LOGGER = logging.getLogger("time_split")
25
-
26
21
 
27
22
  @dataclass(frozen=True)
28
23
  class DatetimeIndexSplitter:
@@ -39,7 +34,6 @@ class DatetimeIndexSplitter:
39
34
  def get_splits(self, available: DatetimeIterable | None = None) -> DatetimeSplits:
40
35
  """Compute a split of given user data."""
41
36
  ms = self._materialize_schedule(available)
42
- self._log_expansion(ms.available_metadata.limits, expanded=ms.available_metadata.expanded_limits)
43
37
  return self._make_bounds_list(ms)
44
38
 
45
39
  def get_plot_data(self, available: DatetimeIterable | None = None) -> tuple[DatetimeSplits, MaterializedSchedule]:
@@ -70,7 +64,7 @@ class DatetimeIndexSplitter:
70
64
  return ms
71
65
 
72
66
  from_end = ms.available_metadata.expanded_limits[1] - (ms.schedule[-1] + Timedelta(self.after))
73
- from_end = from_end.floor(schedule_frequency.base)
67
+ from_end = from_end.floor(schedule_frequency.base) # TODO(settings): should this depend on misc.round_limits?
74
68
 
75
69
  return ms._replace(schedule=ms.schedule + from_end)
76
70
 
@@ -109,7 +103,6 @@ class DatetimeIndexSplitter:
109
103
 
110
104
  Returns:
111
105
  Filtered splits.
112
-
113
106
  """
114
107
  if self.step != 1:
115
108
  step = abs(self.step)
@@ -127,21 +120,6 @@ class DatetimeIndexSplitter:
127
120
 
128
121
  return [s for s in splits if settings.filter(*s)]
129
122
 
130
- def _log_expansion(self, original: LimitsTuple, *, expanded: LimitsTuple) -> None:
131
- from .._frontend import format_expanded_limits
132
-
133
- if original == expanded or not LOGGER.isEnabledFor(logging.INFO):
134
- return
135
-
136
- message = format_expanded_limits(original, expanded=expanded, expand_limits=self.expand_limits)
137
- self._emit_once(message)
138
-
139
- @staticmethod
140
- @lru_cache(8)
141
- def _emit_once(message: str) -> None:
142
- """Emit if **not** in the eight most recent emission messages."""
143
- LOGGER.info(message)
144
-
145
123
  def __post_init__(self) -> None:
146
124
  # Verify n_splits
147
125
  if self.n_splits < 0:
@@ -1,6 +1,5 @@
1
- from typing import Callable, ParamSpec, TypeVar
2
-
3
- from time_split._compat import deprecated_params
1
+ from collections.abc import Callable
2
+ from typing import ParamSpec, TypeVar
4
3
 
5
4
  P = ParamSpec("P")
6
5
  T = TypeVar("T")
@@ -15,12 +14,6 @@ def docs(__func: Callable[P, T], /) -> Callable[P, T]:
15
14
  """Add info the docstring of a function."""
16
15
  assert __func.__doc__ is not None # noqa: S101
17
16
  __func.__doc__ = __func.__doc__.format(**_DOCSTRINGS)
18
-
19
- if isinstance(__func, type):
20
- __func.__init__ = deprecated_params(__func.__init__) # type: ignore[misc]
21
- else:
22
- __func = deprecated_params(__func)
23
-
24
17
  return __func
25
18
 
26
19
 
@@ -1,11 +1,11 @@
1
+ from collections.abc import Sized
1
2
  from dataclasses import asdict, dataclass, replace
2
- from typing import TYPE_CHECKING, Any, Literal, Sized
3
+ from typing import TYPE_CHECKING, Any, Literal
3
4
 
4
5
  import pandas as pd
5
6
  from pandas import Timestamp
6
7
  from rics.misc import format_kwargs, get_public_module
7
-
8
- from time_split._compat import fmt_sec
8
+ from rics.strings import format_seconds as fmt_sec
9
9
 
10
10
  from .._backend import DatetimeIndexSplitter
11
11
  from .._backend._datetime_index_like import DatetimeIndexLike
@@ -65,7 +65,7 @@ def plot(
65
65
  after: Span = 1,
66
66
  step: int = 1,
67
67
  n_splits: int = 0,
68
- available: DatetimeIterable | None = None,
68
+ available: DatetimeIterable | pd.DataFrame | pd.Series | None = None,
69
69
  expand_limits: ExpandLimits = "auto",
70
70
  ignore_filters: bool = False,
71
71
  # Split plot args
@@ -112,6 +112,15 @@ def plot(
112
112
  """
113
113
  import matplotlib.pyplot as plt
114
114
 
115
+ if (
116
+ available is not None
117
+ and hasattr(available, "index")
118
+ and not callable(available.index)
119
+ and hasattr(available, "shape")
120
+ and len(available.shape) != 1
121
+ ):
122
+ available = available.index
123
+
115
124
  splitter = DatetimeIndexSplitter(
116
125
  schedule,
117
126
  before=before,
@@ -121,6 +130,7 @@ def plot(
121
130
  expand_limits=expand_limits,
122
131
  ignore_filters=ignore_filters,
123
132
  )
133
+
124
134
  plot_data = _get_plot_data(available, splitter, row_count_bin=row_count_bin, show_removed=show_removed)
125
135
 
126
136
  if bar_labels is True:
@@ -249,7 +259,7 @@ def _add_bar_labels(
249
259
  unit=unit_or_labels,
250
260
  )
251
261
 
252
- for bar, label in zip(ax.containers, labels):
262
+ for bar, label in zip(ax.containers, labels, strict=True):
253
263
  ax.bar_label(bar, labels=[label], **kwargs) # type: ignore[arg-type]
254
264
 
255
265
 
@@ -6,8 +6,7 @@ from typing import Any, Generic
6
6
 
7
7
  import pandas as pd
8
8
  from rics.collections.dicts import flatten_dict
9
-
10
- from time_split._compat import fmt_sec
9
+ from rics.strings import format_seconds as fmt_sec
11
10
 
12
11
  from ..settings import log_split_progress as settings
13
12
  from ..types import DatetimeSplitBounds, FormatMetrics, GetMetrics, LoggerArg, MetricsType, SplitProgressExtras
@@ -122,7 +121,7 @@ class _ProgressTracker(Generic[MetricsType]):
122
121
  n_splits = len(splits)
123
122
 
124
123
  for n, split in enumerate(splits, start=1):
125
- default_extras = SplitProgressExtras(
124
+ default_extras = SplitProgressExtras[MetricsType](
126
125
  n=n,
127
126
  n_splits=n_splits,
128
127
  start=split.start.isoformat(),
@@ -1,18 +1,19 @@
1
+ import datetime
1
2
  from typing import Any, Literal, overload
2
3
 
3
4
  from pandas import Timestamp
5
+ from rics.strings import format_seconds as fmt_sec
4
6
 
5
7
  from .._backend._limits import LimitsTuple
6
- from .._compat import fmt_sec
7
8
  from ..settings import log_split_progress
8
9
  from ..types import DatetimeSplitBounds, DatetimeTypes, ExpandLimits
9
10
 
10
11
 
11
12
  @overload
12
13
  def to_string(
13
- bounds: DatetimeTypes,
14
- __ignored0: DatetimeTypes,
15
- __ignored1: DatetimeTypes,
14
+ bounds: DatetimeSplitBounds | tuple[DatetimeTypes, DatetimeTypes, DatetimeTypes],
15
+ __ignored0: Literal[None] = None,
16
+ __ignored1: Literal[None] = None,
16
17
  /,
17
18
  *,
18
19
  format: str | None = None,
@@ -21,9 +22,9 @@ def to_string(
21
22
 
22
23
  @overload
23
24
  def to_string(
24
- start: tuple[DatetimeTypes, DatetimeTypes, DatetimeTypes],
25
- mid: Literal[None] = None,
26
- end: Literal[None] = None,
25
+ start: DatetimeTypes,
26
+ mid: DatetimeTypes,
27
+ end: DatetimeTypes,
27
28
  /,
28
29
  *,
29
30
  format: str | None = None,
@@ -31,7 +32,7 @@ def to_string(
31
32
 
32
33
 
33
34
  def to_string(
34
- bounds: DatetimeTypes | DatetimeSplitBounds | tuple[DatetimeTypes, DatetimeTypes, DatetimeTypes],
35
+ start_or_bounds: DatetimeTypes | DatetimeSplitBounds | tuple[DatetimeTypes, DatetimeTypes, DatetimeTypes],
35
36
  mid: DatetimeTypes | None = None,
36
37
  end: DatetimeTypes | None = None,
37
38
  /,
@@ -40,15 +41,10 @@ def to_string(
40
41
  ) -> str:
41
42
  """Pretty-print a fold.
42
43
 
43
- .. code-block:: python
44
- :caption: Sample output.
45
-
46
- ('2021-12-30' <= [schedule: '2022-01-04' (Tuesday)] < '2022-01-04 18:00:00')
47
-
48
44
  Args:
49
- bounds: A fold tuple ``(start, mid, end)``, or just `start` (followed by `mid` and `end`).
50
- mid: Datetime-like. Must be ``None`` when `bounds` is a tuple.
51
- end: Datetime-like. Must be ``None`` when `bounds` is a tuple.
45
+ start_or_bounds: A fold tuple ``(start, mid, end)``, or just `start` (followed by `mid` and `end`).
46
+ mid: Datetime-like. Must be ``None`` when `start_or_bounds` is a tuple.
47
+ end: Datetime-like. Must be ``None`` when `start_or_bounds` is a tuple.
52
48
  format: A custom format to use. Use :attr:`~.settings.log_split_progress.FOLD_FORMAT` if ``None``, but note that
53
49
  only the `start`, `mid` and `end` keys are available to this function.
54
50
 
@@ -58,20 +54,41 @@ def to_string(
58
54
  Raises:
59
55
  TypeError: If an incorrect number of timestamps are given.
60
56
 
57
+ Examples:
58
+ Sample format output.
59
+
60
+ >>> to_string("2021-12-30", "2022-01-04", "2022-01-04 18:00:00")
61
+ "'2021-12-30' <= [schedule: '2022-01-04' (Tuesday)] < '2022-01-04 18:00:00'"
62
+
63
+ The :attr:`default format <.log_split_progress.FOLD_FORMAT>` was used above.
64
+
65
+ Using properties. The `delta` is the distance from `mid` formatted by
66
+ :func:`~rics.strings.format_seconds`. The `date` property returns a :class:`datetime.date` object.
67
+
68
+ >>> to_string(
69
+ ... ("2021-12-30", "2022-01-04", "2022-01-04 18:00:00"),
70
+ ... format="'{start.date}' [-{start.delta}] <= '{mid.iso}' < [+{end.delta}]",
71
+ ... )
72
+ "'2021-12-30' [-5d] <= '2022-01-04T00:00:00' < [+18h]"
73
+
74
+ The delta is always positive; you must add the sign yourself.
61
75
  """
62
- if isinstance(bounds, tuple):
76
+ if isinstance(start_or_bounds, tuple):
63
77
  if not (mid is None and end is None):
64
- raise TypeError(f"Too many arguments: {(bounds, mid, end)}.")
65
- start, mid, end = bounds
78
+ raise TypeError(f"Too many arguments: {(start_or_bounds, mid, end)}.")
79
+ start, mid, end = start_or_bounds
66
80
  else:
67
81
  if mid is None or end is None:
68
- raise TypeError(f"Too few arguments: {(bounds, mid, end)}.")
69
- start = bounds
82
+ raise TypeError(f"Too few arguments: {(start_or_bounds, mid, end)}.")
83
+ start = start_or_bounds
84
+
85
+ if format is None:
86
+ format = log_split_progress.FOLD_FORMAT
70
87
 
71
- return (log_split_progress.FOLD_FORMAT if format is None else format).format(
72
- start=_PrettyTimestamp(start),
73
- mid=_PrettyTimestamp(mid),
74
- end=_PrettyTimestamp(end),
88
+ return format.format(
89
+ start=_PrettyTimestamp(start, mid),
90
+ mid=_PrettyTimestamp(mid, mid),
91
+ end=_PrettyTimestamp(end, mid),
75
92
  )
76
93
 
77
94
 
@@ -95,9 +112,11 @@ def stringify(old: Timestamp, *, new: Timestamp | None = None, diff_only: bool =
95
112
 
96
113
 
97
114
  class _PrettyTimestamp:
98
- def __init__(self, raw: DatetimeTypes) -> None:
115
+ def __init__(self, raw: DatetimeTypes, anchor: DatetimeTypes | None = None) -> None:
99
116
  self.timestamp = Timestamp(raw)
117
+ self._anchor = None if anchor is None else Timestamp(anchor)
100
118
  self._auto: str | None = None
119
+ self._delta: str | None = None
101
120
 
102
121
  def __getattr__(self, name: str) -> Any:
103
122
  return getattr(self.timestamp, name)
@@ -123,6 +142,25 @@ class _PrettyTimestamp:
123
142
  self._auto = format(timestamp, format_spec)
124
143
  return self._auto
125
144
 
145
+ @property
146
+ def delta(self) -> str:
147
+ if self._anchor is None:
148
+ raise ValueError("Delta requires an anchor.")
149
+
150
+ if self._delta is None:
151
+ seconds = (self.timestamp - self._anchor).round("s").total_seconds()
152
+ self._delta = fmt_sec(abs(seconds))
153
+
154
+ return self._delta
155
+
156
+ @property
157
+ def iso(self) -> str:
158
+ return self.timestamp.isoformat() # type: ignore[no-any-return]
159
+
160
+ @property
161
+ def date(self) -> datetime.date:
162
+ return self.timestamp.date() # type: ignore[no-any-return]
163
+
126
164
 
127
165
  def format_expanded_limits(
128
166
  original: LimitsTuple,
@@ -0,0 +1,11 @@
1
+ """Supporting functions for the Streamlit companion app.
2
+
3
+ The app may be installed by running ``pip install time-split[app]``. The application API is *not* stable, but will be
4
+ available under the ``time_split_app`` namespace. Repo: https://github.com/rsundqvist/time-split-app/.
5
+ """
6
+
7
+ from ._create_explorer_link import create_explorer_link
8
+
9
+ __all__ = [
10
+ "create_explorer_link",
11
+ ]
@@ -1,3 +1,4 @@
1
+ from base64 import b16encode as encode_binary_data
1
2
  from datetime import datetime
2
3
  from typing import Any, Unpack
3
4
  from urllib.parse import ParseResult, urlencode, urlparse, urlunparse
@@ -10,20 +11,21 @@ from ..types import DatetimeIndexSplitterKwargs, DatetimeIterable
10
11
  @docs
11
12
  def create_explorer_link(
12
13
  host: str,
13
- data: DatetimeIterable | str | None = None,
14
- available: DatetimeIterable | str | None = None,
14
+ data: DatetimeIterable | str | bytes | None = None,
15
+ available: DatetimeIterable | str | bytes | None = None,
15
16
  *,
16
17
  show_removed: bool = True,
17
18
  skip_default: bool = False,
18
19
  **kwargs: Unpack[DatetimeIndexSplitterKwargs],
19
20
  ) -> str:
20
- """Create a `Time Fold Explorer` application URL.
21
+ """Create an application URL.
21
22
 
22
23
  Args:
23
24
  host: Base address where the application is hosted.
24
25
  data: {available} Regular `available` arguments (as passed to e.g. :func:`time_split.split`) are encoded as
25
- a date range to generate dummy data for. Pass a ``str`` to use dataset bundled by the server instead. Note
26
- that this function cannot verify the `kwargs` if `available` is a ``str`` dataset.
26
+ a date range to generate dummy data for.
27
+ Pass a ``str`` to use a bundled dataset, or ``bytes`` to use a custom loader. Note that this will disable
28
+ verification of the `**kwargs`.
27
29
  available: Alias of `data`.
28
30
  skip_default: If ``True``, do not include default split params in the link.
29
31
  show_removed: {show_removed}
@@ -54,19 +56,20 @@ def create_explorer_link(
54
56
  :target: https://hub.docker.com/r/rsundqvist/time-split/
55
57
  :alt: Docker Image Size (tag)
56
58
  """
57
- if sum([data is None, available is None]) != 1:
58
- raise ValueError("Exactly one of `data` and `available` must be given.")
59
59
  if data is None:
60
+ if available is None:
61
+ raise ValueError("Exactly one of `data` and `available` must be given.")
60
62
  data = available
63
+ elif data is not None and available is not None:
64
+ raise ValueError("Exactly one of `data` and `available` must be given.")
61
65
 
62
- assert data is not None # noqa: S101
63
66
  pr = _create_explorer_link(host, data, show_removed=show_removed, skip_default=skip_default, **kwargs)
64
67
  return urlunparse(pr)
65
68
 
66
69
 
67
70
  def _create_explorer_link(
68
71
  host: str,
69
- data: DatetimeIterable | str,
72
+ data: DatetimeIterable | str | bytes,
70
73
  *,
71
74
  show_removed: bool = True,
72
75
  skip_default: bool = False,
@@ -81,7 +84,7 @@ def _create_explorer_link(
81
84
 
82
85
 
83
86
  def _create_query(
84
- data: DatetimeIterable | str,
87
+ data: DatetimeIterable | str | bytes,
85
88
  *,
86
89
  skip_default: bool,
87
90
  show_removed: bool,
@@ -89,6 +92,9 @@ def _create_query(
89
92
  ) -> dict[str, Any]:
90
93
  query: dict[str, Any] = {}
91
94
 
95
+ if isinstance(data, bytes):
96
+ data = "0x" + encode_binary_data(data).decode()
97
+
92
98
  if isinstance(data, str):
93
99
  query = {"data": data, **kwargs}
94
100
  else:
@@ -0,0 +1,58 @@
1
+ """Reexported resources from the ``time_split_app`` namespace.
2
+
3
+ Running ``python -m time_split app new`` will create a ``Dockerfile`` and a simple ``my_extensions.py`` module.
4
+
5
+ Environment variables:
6
+ Subset of variables used to customize Docker-based deployments.
7
+
8
+ .. envvar:: DATASET_LOADER
9
+
10
+ A :class:`DataLoaderWidget` instance or type, e.g. ``my_extensions:MyDatasetLoader``.
11
+
12
+ .. envvar:: SPLIT_SELECT_FN
13
+
14
+ A :attr:`SelectSplitParams`-function, e.g. ``my_extensions:my_select_fn``.
15
+
16
+ .. envvar:: PLOT_FN
17
+
18
+ A :func:`~time_split.plot`-like function, e.g. ``my_extensions:my_plot_fn``.
19
+
20
+ .. envvar:: LINK_FN
21
+
22
+ A :func:`.create_explorer_link`-like function, e.g. ``my_extensions:my_link_fn``.
23
+
24
+ See https://github.com/rsundqvist/time-split-app/blob/master/src/time_split_app/config.py
25
+ for more variable-based configuration options.
26
+ """
27
+
28
+ from time_split_app.datasets import Dataset, DatasetConfig, DuplicateIndexError, load_dataset, load_dataset_configs
29
+ from time_split_app.widgets import DataLoaderWidget, DurationWidget, select_datetime, select_duration
30
+ from time_split_app.widgets import types as _types
31
+ from time_split_app.widgets.types import QueryParams
32
+
33
+ SelectSplitParams = _types.SelectSplitParams
34
+ """Type of :envvar:`SPLIT_SELECT_FN`.
35
+
36
+ A callable ``() -> split_params``; see :func:`time_split.split`
37
+ and :attr:`~time_split.types.DatetimeIndexSplitterKwargs`.
38
+ """
39
+ PlotFn = _types.PlotFn
40
+ """Type of :envvar:`PLOT_FN`."""
41
+ LinkFn = _types.LinkFn
42
+ """Type of :envvar:`LINK_FN`."""
43
+
44
+ __all__ = [
45
+ "DataLoaderWidget",
46
+ "Dataset",
47
+ "DatasetConfig",
48
+ "DuplicateIndexError",
49
+ "DurationWidget",
50
+ "LinkFn",
51
+ "PlotFn",
52
+ "QueryParams",
53
+ "SelectSplitParams",
54
+ "load_dataset",
55
+ "load_dataset_configs",
56
+ "select_datetime",
57
+ "select_duration",
58
+ ]
@@ -0,0 +1,29 @@
1
+ """CLI entrypoint.
2
+
3
+ Use ``python -m time_split --help`` to get started.
4
+ """
5
+
6
+ try:
7
+ import click as _c
8
+ from rics.click import _alias_command_group
9
+ from time_split_app.cli import main as _app_main
10
+ except ImportError as e:
11
+ name = "time-split[app]"
12
+ exc = ImportError(f"Install `{name}` to use.", name=name)
13
+ exc.add_note(f"Package `{e.name}` not installed. Run:\n `pip install {name}`\nto use the CLI entrypoint.")
14
+ raise exc from e
15
+
16
+
17
+ @_c.group(
18
+ name="time-split",
19
+ epilog="Visit https://time-split.readthedocs.io/ for help.",
20
+ cls=_alias_command_group.AliasedCommandGroup,
21
+ )
22
+ def main() -> None:
23
+ """See https://time-split.readthedocs.io/."""
24
+
25
+
26
+ main.add_command(_app_main)
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -177,3 +177,33 @@ class misc: # noqa: N801
177
177
 
178
178
  filter: _t.Callable[[_pd.Timestamp, _pd.Timestamp, _pd.Timestamp], bool] | None = None
179
179
  """A callable ``(start, mid, end) -> bool`` applied to all generated folds."""
180
+
181
+ round_limits: bool = False
182
+ """If ``True``, use rounding instead of ``floor`` and ``ceil`` for limits expansion.
183
+
184
+ Normally, limits such as
185
+
186
+ >>> ('2019-04-10 23:55:00', '2019-05-12 01:00:00')
187
+
188
+ will never be rounded to
189
+
190
+ >>> ('2019-04-11', '2019-05-12')
191
+
192
+ since that wouldn't be an expansion of the limits. Setting ``round_limits=True`` will allow the
193
+ :func:`~time_split.support.expand_limits` function round these bounds inward to the nearest dates.
194
+ """
195
+
196
+
197
+ def no_init(self: object) -> None:
198
+ """Prevents initialization of config classes."""
199
+ raise TypeError(
200
+ f"Class '{__package__}.{type(self).__name__}' is used as a public"
201
+ f" namespace. There is no need to instantiate this class."
202
+ )
203
+
204
+
205
+ for cls in auto_expand_limits, plot, log_split_progress, misc:
206
+ cls.__init__ = no_init # type: ignore[method-assign]
207
+
208
+ del cls
209
+ del no_init
@@ -13,15 +13,13 @@ dependencies if you need to use the ``support`` module.
13
13
 
14
14
  from .._backend import DatetimeIndexSplitter, expand_limits, process_available
15
15
  from .._frontend import default_metrics_formatter, fold_weight, format_expanded_limits, to_string
16
- from ._create_explorer_link import create_explorer_link
17
16
 
18
17
  __all__ = [
19
18
  "DatetimeIndexSplitter",
20
- "expand_limits",
21
- "process_available",
22
19
  "default_metrics_formatter",
20
+ "expand_limits",
23
21
  "fold_weight",
24
- "to_string",
25
22
  "format_expanded_limits",
26
- "create_explorer_link",
23
+ "process_available",
24
+ "to_string",
27
25
  ]
@@ -86,7 +86,7 @@ class LogSplitProgressKwargs(_t.TypedDict, _t.Generic[MetricsType], total=False)
86
86
  get_metrics: GetMetrics[MetricsType]
87
87
 
88
88
 
89
- class SplitProgressExtras(_t.TypedDict):
89
+ class SplitProgressExtras(_t.TypedDict, _t.Generic[MetricsType]):
90
90
  """Named `extras` used for messages logged by :func:`.log_split_progress`."""
91
91
 
92
92
  n: int
@@ -101,3 +101,5 @@ class SplitProgressExtras(_t.TypedDict):
101
101
  """An :meth:`ISO-formatted <pandas.Timestamp.isoformat>` timestamp (see :attr:`.DatetimeSplitBounds.end`)."""
102
102
  seconds: _t.NotRequired[float]
103
103
  """User time for the fold. Available only for the :attr:`fold-end message <.settings.log_split_progress.END_MESSAGE>`."""
104
+ metrics: _t.NotRequired[MetricsType]
105
+ """Optional fold metrics. Typically appended to the :attr:`fold-end message <.settings.log_split_progress.END_MESSAGE>`."""
@@ -1,49 +0,0 @@
1
- """Vendored functions."""
2
-
3
- from functools import wraps
4
- from typing import Callable, ParamSpec, TypeVar
5
- from warnings import warn
6
-
7
- from rics.misc import tname
8
-
9
- try:
10
- from rics import strings
11
-
12
- fmt_perf = strings.format_perf_counter
13
- fmt_sec = strings.format_seconds
14
- except ImportError:
15
- from rics import performance
16
-
17
- # TODO(0.5.0): Drop this.
18
- fmt_perf = performance.format_perf_counter
19
- fmt_sec = performance.format_seconds
20
-
21
- P = ParamSpec("P")
22
- T = TypeVar("T")
23
-
24
- PARAMS = (("expand_limits", "flex"),)
25
- WARNED: set[str] = set() # Minimize the amount of warnings - once per param is enough.
26
-
27
-
28
- def deprecated_params(__func: Callable[P, T], /) -> Callable[P, T]:
29
- @wraps(__func)
30
- def wrap(*args: P.args, **kwargs: P.kwargs) -> T:
31
- for new, old in PARAMS:
32
- if old in kwargs:
33
- if new in kwargs:
34
- name = tname(__func, prefix_classname=True)
35
- msg = f"Ambiguous call: {name}() does not accept both `{old}` and `{new}`."
36
- raise TypeError(msg)
37
-
38
- if old not in WARNED:
39
- WARNED.add(old)
40
-
41
- name = tname(__func, prefix_classname=True)
42
- msg = f"{name}(): The `{old}` parameter is deprecated; use `{new}` instead."
43
- warn(message=msg, category=DeprecationWarning, stacklevel=2)
44
-
45
- kwargs[new] = kwargs.pop(old)
46
-
47
- return __func(*args, **kwargs)
48
-
49
- return wrap
File without changes
@@ -7,6 +7,6 @@ __all__ = [
7
7
  "DatetimeIndexLike",
8
8
  "DatetimeIndexSplitter",
9
9
  "ProcessAvailableResult",
10
- "process_available",
11
10
  "expand_limits",
11
+ "process_available",
12
12
  ]
@@ -5,11 +5,11 @@ from ._to_string import format_expanded_limits, to_string
5
5
  from ._weight import fold_weight
6
6
 
7
7
  __all__ = [
8
+ "default_metrics_formatter",
8
9
  "fold_weight",
10
+ "format_expanded_limits",
9
11
  "log_split_progress",
10
- "default_metrics_formatter",
11
12
  "plot",
12
13
  "split",
13
14
  "to_string",
14
- "format_expanded_limits",
15
15
  ]