python-utils 4.0.1__tar.gz → 4.1.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 (50) hide show
  1. {python_utils-4.0.1 → python_utils-4.1.1}/PKG-INFO +22 -9
  2. {python_utils-4.0.1 → python_utils-4.1.1}/README.md +20 -8
  3. python_utils-4.1.1/_python_utils_tests/clock.py +56 -0
  4. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_aio.py +37 -0
  5. python_utils-4.1.1/_python_utils_tests/test_containers.py +1211 -0
  6. python_utils-4.1.1/_python_utils_tests/test_converters.py +39 -0
  7. python_utils-4.1.1/_python_utils_tests/test_decorators.py +178 -0
  8. python_utils-4.1.1/_python_utils_tests/test_formatters.py +118 -0
  9. python_utils-4.1.1/_python_utils_tests/test_generators.py +312 -0
  10. python_utils-4.1.1/_python_utils_tests/test_import.py +205 -0
  11. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_lazy_imports.py +5 -1
  12. python_utils-4.1.1/_python_utils_tests/test_logged.py +224 -0
  13. python_utils-4.1.1/_python_utils_tests/test_logger.py +144 -0
  14. python_utils-4.1.1/_python_utils_tests/test_time.py +587 -0
  15. python_utils-4.1.1/conftest.py +8 -0
  16. {python_utils-4.0.1 → python_utils-4.1.1}/pyproject.toml +3 -1
  17. {python_utils-4.0.1 → python_utils-4.1.1}/pyproject.toml.orig +3 -2
  18. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/aio.py +5 -2
  19. python_utils-4.1.1/python_utils/containers.py +1133 -0
  20. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/converters.py +70 -15
  21. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/decorators.py +29 -3
  22. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/formatters.py +34 -12
  23. python_utils-4.1.1/python_utils/generators.py +174 -0
  24. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/import_.py +49 -8
  25. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/logger.py +89 -30
  26. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/loguru.py +34 -1
  27. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/time.py +55 -26
  28. {python_utils-4.0.1 → python_utils-4.1.1}/tox.ini +12 -0
  29. python_utils-4.0.1/_python_utils_tests/test_containers.py +0 -80
  30. python_utils-4.0.1/_python_utils_tests/test_decorators.py +0 -82
  31. python_utils-4.0.1/_python_utils_tests/test_generators.py +0 -76
  32. python_utils-4.0.1/_python_utils_tests/test_import.py +0 -62
  33. python_utils-4.0.1/_python_utils_tests/test_logger.py +0 -24
  34. python_utils-4.0.1/_python_utils_tests/test_time.py +0 -208
  35. python_utils-4.0.1/python_utils/containers.py +0 -637
  36. python_utils-4.0.1/python_utils/generators.py +0 -126
  37. {python_utils-4.0.1 → python_utils-4.1.1}/LICENSE +0 -0
  38. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/__init__.py +0 -0
  39. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/requirements.txt +0 -0
  40. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_aliases.py +0 -0
  41. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_import_footprint.py +0 -0
  42. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_python_utils.py +0 -0
  43. {python_utils-4.0.1 → python_utils-4.1.1}/_python_utils_tests/test_types.py +0 -0
  44. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/__about__.py +0 -0
  45. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/__init__.py +0 -0
  46. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/_aliases.py +0 -0
  47. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/exceptions.py +0 -0
  48. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/py.typed +0 -0
  49. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/terminal.py +0 -0
  50. {python_utils-4.0.1 → python_utils-4.1.1}/python_utils/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-utils
3
- Version: 4.0.1
3
+ Version: 4.1.1
4
4
  Summary: Python Utils is a module with some convenient utilities not included with the standard Python install
5
5
  Keywords: utils,utilities,helpers,typing,typed,async,lazy-imports
6
6
  Author: Rick van Hattem
@@ -35,6 +35,7 @@ Project-URL: Homepage, https://github.com/WoLpH/python-utils
35
35
  Project-URL: Documentation, https://python-utils.readthedocs.io/
36
36
  Project-URL: Repository, https://github.com/WoLpH/python-utils
37
37
  Project-URL: Changelog, https://github.com/WoLpH/python-utils/releases
38
+ Project-URL: Funding, https://github.com/sponsors/wolph
38
39
  Provides-Extra: docs
39
40
  Provides-Extra: loguru
40
41
  Provides-Extra: tests
@@ -48,14 +49,14 @@ Description-Content-Type: text/markdown
48
49
 
49
50
  **The fast, fully-typed stdlib helpers you keep rewriting — in one tiny, dependency-light package.**
50
51
 
51
- [![PyPI version](https://img.shields.io/pypi/v/python-utils.svg?logo=pypi&logoColor=white)](https://pypi.python.org/pypi/python-utils)
52
- [![Python versions](https://img.shields.io/pypi/pyversions/python-utils.svg?logo=python&logoColor=white)](https://pypi.python.org/pypi/python-utils)
53
- [![CI](https://github.com/WoLpH/python-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/WoLpH/python-utils/actions/workflows/ci.yml)
54
- [![Coverage Status](https://coveralls.io/repos/github/WoLpH/python-utils/badge.svg?branch=develop)](https://coveralls.io/github/WoLpH/python-utils?branch=develop)
55
- [![Typed](https://img.shields.io/badge/typed-mypy%20%7C%20pyright%20%7C%20pyrefly-blue.svg)](https://github.com/WoLpH/python-utils)
56
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
57
- [![License](https://img.shields.io/pypi/l/python-utils.svg)](https://github.com/WoLpH/python-utils/blob/develop/LICENSE)
58
- [![Downloads](https://img.shields.io/pypi/dm/python-utils.svg?logo=pypi&logoColor=white)](https://pypi.python.org/pypi/python-utils)
52
+ [![PyPI version](https://img.shields.io/pypi/v/python-utils.svg?logo=pypi&logoColor=white&style=flat-square&labelColor=555&color=007ec6)](https://pypi.python.org/pypi/python-utils)
53
+ [![Python versions](https://img.shields.io/pypi/pyversions/python-utils.svg?logo=python&logoColor=white&style=flat-square&labelColor=555&color=007ec6)](https://pypi.python.org/pypi/python-utils)
54
+ [![CI on master](https://img.shields.io/github/actions/workflow/status/wolph/python-utils/ci.yml?branch=master&label=CI&style=flat-square&labelColor=555)](https://github.com/wolph/python-utils/actions/workflows/ci.yml?query=branch%3Amaster)
55
+ [![Coverage on master](https://img.shields.io/coverallsCoverage/github/wolph/python-utils?branch=master&style=flat-square&labelColor=555)](https://coveralls.io/github/wolph/python-utils?branch=master)
56
+ [Typed: mypy, pyright and pyrefly](https://github.com/WoLpH/python-utils)
57
+ [![Ruff](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fastral-sh%2Fruff%2Fmain%2Fassets%2Fbadge%2Fv2.json&style=flat-square&labelColor=555&color=007ec6)](https://github.com/astral-sh/ruff)
58
+ [![License](https://img.shields.io/pypi/l/python-utils.svg?style=flat-square&labelColor=555&color=007ec6)](https://github.com/WoLpH/python-utils/blob/develop/LICENSE)
59
+ [![Monthly downloads](https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fapi.pepy.tech%2Fbadge%2Fpython-utils%2Fmonth&query=%28%2F%2F%2A%5Blocal-name%28%29%3D%22text%22%5D%29%5Blast%28%29%5D&label=downloads%2Fmonth&style=flat-square&labelColor=555&color=007ec6)](https://pepy.tech/projects/python-utils)
59
60
 
60
61
  [**Documentation**](https://python-utils.readthedocs.io/en/latest/) ·
61
62
  [**PyPI**](https://pypi.python.org/pypi/python-utils) ·
@@ -382,6 +383,18 @@ Contributions are very welcome! We keep a strict **100% coverage** bar and run
382
383
  [CONTRIBUTING.md](https://github.com/WoLpH/python-utils/blob/develop/CONTRIBUTING.md)
383
384
  to get set up.
384
385
 
386
+ ## Support
387
+
388
+ python-utils is maintained by [Rick van Hattem](https://github.com/wolph) in his own time. It is a small library that four other packages depend on, which means every change is a compatibility question.
389
+
390
+ If it saved you an afternoon, a tip covers an hour of issue triage:
391
+ [Ko-fi](https://ko-fi.com/wolph_gh) or [GitHub Sponsors](https://github.com/sponsors/wolph).
392
+
393
+ If your company funds its dependencies, this package is on
394
+ [thanks.dev](https://thanks.dev/u/gh/wolph).
395
+
396
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/wolph_gh)
397
+
385
398
  ## 📄 License
386
399
 
387
400
  BSD-3-Clause — see [LICENSE](https://github.com/WoLpH/python-utils/blob/develop/LICENSE).
@@ -6,14 +6,14 @@
6
6
 
7
7
  **The fast, fully-typed stdlib helpers you keep rewriting — in one tiny, dependency-light package.**
8
8
 
9
- [![PyPI version](https://img.shields.io/pypi/v/python-utils.svg?logo=pypi&logoColor=white)](https://pypi.python.org/pypi/python-utils)
10
- [![Python versions](https://img.shields.io/pypi/pyversions/python-utils.svg?logo=python&logoColor=white)](https://pypi.python.org/pypi/python-utils)
11
- [![CI](https://github.com/WoLpH/python-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/WoLpH/python-utils/actions/workflows/ci.yml)
12
- [![Coverage Status](https://coveralls.io/repos/github/WoLpH/python-utils/badge.svg?branch=develop)](https://coveralls.io/github/WoLpH/python-utils?branch=develop)
13
- [![Typed](https://img.shields.io/badge/typed-mypy%20%7C%20pyright%20%7C%20pyrefly-blue.svg)](https://github.com/WoLpH/python-utils)
14
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
15
- [![License](https://img.shields.io/pypi/l/python-utils.svg)](https://github.com/WoLpH/python-utils/blob/develop/LICENSE)
16
- [![Downloads](https://img.shields.io/pypi/dm/python-utils.svg?logo=pypi&logoColor=white)](https://pypi.python.org/pypi/python-utils)
9
+ [![PyPI version](https://img.shields.io/pypi/v/python-utils.svg?logo=pypi&logoColor=white&style=flat-square&labelColor=555&color=007ec6)](https://pypi.python.org/pypi/python-utils)
10
+ [![Python versions](https://img.shields.io/pypi/pyversions/python-utils.svg?logo=python&logoColor=white&style=flat-square&labelColor=555&color=007ec6)](https://pypi.python.org/pypi/python-utils)
11
+ [![CI on master](https://img.shields.io/github/actions/workflow/status/wolph/python-utils/ci.yml?branch=master&label=CI&style=flat-square&labelColor=555)](https://github.com/wolph/python-utils/actions/workflows/ci.yml?query=branch%3Amaster)
12
+ [![Coverage on master](https://img.shields.io/coverallsCoverage/github/wolph/python-utils?branch=master&style=flat-square&labelColor=555)](https://coveralls.io/github/wolph/python-utils?branch=master)
13
+ [Typed: mypy, pyright and pyrefly](https://github.com/WoLpH/python-utils)
14
+ [![Ruff](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fastral-sh%2Fruff%2Fmain%2Fassets%2Fbadge%2Fv2.json&style=flat-square&labelColor=555&color=007ec6)](https://github.com/astral-sh/ruff)
15
+ [![License](https://img.shields.io/pypi/l/python-utils.svg?style=flat-square&labelColor=555&color=007ec6)](https://github.com/WoLpH/python-utils/blob/develop/LICENSE)
16
+ [![Monthly downloads](https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fapi.pepy.tech%2Fbadge%2Fpython-utils%2Fmonth&query=%28%2F%2F%2A%5Blocal-name%28%29%3D%22text%22%5D%29%5Blast%28%29%5D&label=downloads%2Fmonth&style=flat-square&labelColor=555&color=007ec6)](https://pepy.tech/projects/python-utils)
17
17
 
18
18
  [**Documentation**](https://python-utils.readthedocs.io/en/latest/) ·
19
19
  [**PyPI**](https://pypi.python.org/pypi/python-utils) ·
@@ -340,6 +340,18 @@ Contributions are very welcome! We keep a strict **100% coverage** bar and run
340
340
  [CONTRIBUTING.md](https://github.com/WoLpH/python-utils/blob/develop/CONTRIBUTING.md)
341
341
  to get set up.
342
342
 
343
+ ## Support
344
+
345
+ python-utils is maintained by [Rick van Hattem](https://github.com/wolph) in his own time. It is a small library that four other packages depend on, which means every change is a compatibility question.
346
+
347
+ If it saved you an afternoon, a tip covers an hour of issue triage:
348
+ [Ko-fi](https://ko-fi.com/wolph_gh) or [GitHub Sponsors](https://github.com/sponsors/wolph).
349
+
350
+ If your company funds its dependencies, this package is on
351
+ [thanks.dev](https://thanks.dev/u/gh/wolph).
352
+
353
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/wolph_gh)
354
+
343
355
  ## 📄 License
344
356
 
345
357
  BSD-3-Clause — see [LICENSE](https://github.com/WoLpH/python-utils/blob/develop/LICENSE).
@@ -0,0 +1,56 @@
1
+ """A fake clock for the tests and doctests of ``python_utils.time``."""
2
+
3
+ import typing
4
+
5
+ import pytest
6
+
7
+ import python_utils.time
8
+
9
+ #: The doctest that sleeps on the clock, by the name pytest gives it.
10
+ TIMEOUT_GENERATOR_DOCTEST: str = 'python_utils.time.timeout_generator'
11
+
12
+
13
+ class FakeClock:
14
+ """
15
+ A clock that only moves when something sleeps on it.
16
+
17
+ ``time.sleep`` promises to sleep at least as long as requested. A busy
18
+ machine sleeps tens of milliseconds longer, and that changes how many
19
+ items ``timeout_generator`` yields before its timeout. On this clock the
20
+ number of items depends on the arguments alone.
21
+
22
+ Attributes:
23
+ now (float): The current time in seconds.
24
+ sleeps (list[float]): Every requested sleep, in order.
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ """Start at zero without any recorded sleeps."""
29
+ self.now: float = 0.0
30
+ self.sleeps: list[float] = []
31
+
32
+ def perf_counter(self) -> float:
33
+ """Return the current time, like ``time.perf_counter``."""
34
+ return self.now
35
+
36
+ def sleep(self, seconds: float) -> None:
37
+ """Record the sleep and move the clock forward, without waiting."""
38
+ self.sleeps.append(seconds)
39
+ self.now += seconds
40
+
41
+
42
+ @pytest.fixture
43
+ def fake_clock(monkeypatch: pytest.MonkeyPatch) -> FakeClock:
44
+ """Replace the ``time`` module inside ``python_utils.time``."""
45
+ clock: FakeClock = FakeClock()
46
+ monkeypatch.setattr(python_utils.time, 'time', clock)
47
+ return clock
48
+
49
+
50
+ @pytest.fixture(autouse=True)
51
+ def fake_clock_in_doctest(request: pytest.FixtureRequest) -> None:
52
+ """Run the ``timeout_generator`` doctest on the fake clock."""
53
+ # pytest leaves `FixtureRequest.node` without a type.
54
+ node: pytest.Item = typing.cast(pytest.Item, request.node)
55
+ if node.name == TIMEOUT_GENERATOR_DOCTEST:
56
+ request.getfixturevalue('fake_clock')
@@ -25,6 +25,43 @@ async def test_acount(monkeypatch: pytest.MonkeyPatch) -> None:
25
25
  assert sum(sleeps) == 4
26
26
 
27
27
 
28
+ @pytest.mark.asyncio
29
+ @pytest.mark.parametrize(
30
+ 'start,step,stop,expected',
31
+ [
32
+ (10, -2, 0, [10, 8, 6, 4, 2]),
33
+ (0, -1, -3, [0, -1, -2]),
34
+ (1.5, -0.5, 0, [1.5, 1.0, 0.5]),
35
+ # Nothing to count when `start` is already past `stop`, in either
36
+ # direction. This is what `range` does as well.
37
+ (0, -1, 5, []),
38
+ (5, 1, 3, []),
39
+ (0, 2, 5, [0, 2, 4]),
40
+ ],
41
+ )
42
+ async def test_acount_stop_follows_step_direction(
43
+ start: float, step: float, stop: float, expected: types.List[float]
44
+ ) -> None:
45
+ """Count down to a lower ``stop`` when the step is negative."""
46
+ limit: int = len(expected) + 5
47
+ items: types.List[float] = []
48
+ item: float
49
+ # `acount` is annotated as a plain iterator, closing takes a generator.
50
+ counter: types.AsyncGenerator[float, None] = types.cast(
51
+ types.AsyncGenerator[float, None],
52
+ aio.acount(start=start, step=step, stop=stop),
53
+ )
54
+ async for item in counter:
55
+ items.append(item)
56
+ # A counter that misses its `stop` never ends by itself.
57
+ if len(items) == limit:
58
+ break
59
+
60
+ await counter.aclose()
61
+
62
+ assert items == expected
63
+
64
+
28
65
  @pytest.mark.asyncio
29
66
  async def test_acontainer() -> None:
30
67
  """Collect an async iterable into the requested container."""