pelutils 4.4.0__tar.gz → 4.5.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 (72) hide show
  1. pelutils-4.5.0/LICENSE.txt +19 -0
  2. {pelutils-4.4.0 → pelutils-4.5.0}/PKG-INFO +55 -24
  3. {pelutils-4.4.0 → pelutils-4.5.0}/README.md +37 -15
  4. pelutils-4.5.0/pelutils/__init__.py +5 -0
  5. pelutils-4.5.0/pelutils/array/_unique.pyi +80 -0
  6. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/logging/_rich_string.py +3 -11
  7. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/__init__.py +1 -1
  8. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_table.py +45 -15
  9. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/serialization/_universal_json_model.py +15 -6
  10. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/ticktock/_ticktock.py +15 -2
  11. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils.egg-info/PKG-INFO +55 -24
  12. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils.egg-info/SOURCES.txt +3 -1
  13. pelutils-4.5.0/pelutils.egg-info/requires.txt +10 -0
  14. pelutils-4.5.0/pyproject.toml +86 -0
  15. pelutils-4.5.0/setup.py +17 -0
  16. pelutils-4.4.0/pelutils/__init__.py +0 -3
  17. pelutils-4.4.0/pelutils/__version__.py +0 -2
  18. pelutils-4.4.0/pelutils.egg-info/requires.txt +0 -33
  19. pelutils-4.4.0/setup.py +0 -95
  20. {pelutils-4.4.0 → pelutils-4.5.0}/MANIFEST.in +0 -0
  21. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/__init__.py +0 -0
  22. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/array/blob.c +0 -0
  23. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/array/blob.h +0 -0
  24. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/array/unique.c +0 -0
  25. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/array/unique.h +0 -0
  26. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/deque/deque.c +0 -0
  27. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/deque/deque.h +0 -0
  28. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/hashmap/LICENSE +0 -0
  29. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/hashmap/hashmap.c +0 -0
  30. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/hashmap/hashmap.h +0 -0
  31. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/pelutils.c +0 -0
  32. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_c/types.h +0 -0
  33. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_entry_points/__init__.py +0 -0
  34. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/_entry_points/linecounter.py +0 -0
  35. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/array/__init__.py +0 -0
  36. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/array/_blob.py +0 -0
  37. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/array/_unique.py +0 -0
  38. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/array/_utils.py +0 -0
  39. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/job_parser/__init__.py +0 -0
  40. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/job_parser/_job_parser.py +0 -0
  41. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/job_parser/_structs.py +0 -0
  42. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/legacy/__init__.py +0 -0
  43. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/legacy/v3/__init__.py +0 -0
  44. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/legacy/v3/_datastorage.py +0 -0
  45. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/logging/__init__.py +0 -0
  46. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/logging/_logger.py +0 -0
  47. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/logging/_rotate.py +0 -0
  48. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_conditional_import.py +0 -0
  49. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_files.py +0 -0
  50. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_git.py +0 -0
  51. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_misc.py +0 -0
  52. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/misc/_platform.py +0 -0
  53. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/plots/__init__.py +0 -0
  54. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/plots/_figure.py +0 -0
  55. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/plots/_histogram.py +0 -0
  56. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/plots/_utils.py +0 -0
  57. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/py.typed +0 -0
  58. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/serialization/__init__.py +0 -0
  59. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/serialization/_jsonl.py +0 -0
  60. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/serialization/_pretty_json.py +0 -0
  61. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/stats/__init__.py +0 -0
  62. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/stats/_distributions.py +0 -0
  63. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/stats/_stats.py +0 -0
  64. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/tests/__init__.py +0 -0
  65. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/tests/_tests.py +0 -0
  66. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/ticktock/__init__.py +0 -0
  67. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/types/__init__.py +0 -0
  68. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils/types/_types.py +0 -0
  69. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils.egg-info/dependency_links.txt +0 -0
  70. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils.egg-info/entry_points.txt +0 -0
  71. {pelutils-4.4.0 → pelutils-4.5.0}/pelutils.egg-info/top_level.txt +0 -0
  72. {pelutils-4.4.0 → pelutils-4.5.0}/setup.cfg +0 -0
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2022 Asger Laurits Schultz and Søren Winkel Holm
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pelutils
3
- Version: 4.4.0
3
+ Version: 4.5.0
4
4
  Summary: The Swiss army knife of Python projects
5
- Home-page: https://github.com/peleiden/pelutils
6
- Download-URL: https://pypi.org/project/pelutils/
7
- Author: Asger Laurits Schultz, Søren Winkel Holm
8
- Author-email: asger.s@protonmail.com, swholm@protonmail.com
9
- License: MIT
5
+ Author-email: Asger Laurits Schultz <asger.s@protonmail.com>, Søren Winkel Holm <swholm@protonmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/peleiden/pelutils
8
+ Project-URL: Download, https://pypi.org/project/pelutils/
10
9
  Keywords: utility,logger,parser,profiling,plotting
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
@@ -20,9 +19,19 @@ Classifier: Programming Language :: Python :: 3.14
20
19
  Classifier: Programming Language :: Python :: 3.15
21
20
  Requires-Python: >=3.11
22
21
  Description-Content-Type: text/markdown
23
- Provides-Extra: dev
24
- Provides-Extra: docs
22
+ License-File: LICENSE.txt
25
23
  License-File: pelutils/_c/hashmap/LICENSE
24
+ Requires-Dist: numpy>=2.0
25
+ Requires-Dist: gitpython>=3.1.0
26
+ Requires-Dist: rich>=10.0.0
27
+ Requires-Dist: py-cpuinfo>=8.0.0
28
+ Requires-Dist: psutil>=5.8.0
29
+ Requires-Dist: matplotlib>=3.3
30
+ Requires-Dist: scipy>=1.6
31
+ Requires-Dist: tqdm>=4.55
32
+ Requires-Dist: pydantic>=2
33
+ Requires-Dist: typing_extensions>=4.6
34
+ Dynamic: license-file
26
35
 
27
36
  # pelutils
28
37
 
@@ -314,7 +323,7 @@ def process(features: FloatArray, labels: IntArray, mask: BoolArray): ...
314
323
 
315
324
  ## Also included
316
325
 
317
- - `pelutils.misc.Table` — build aligned text tables which can also be easily export to LaTeX with `Table.to_latex()`.
326
+ - `pelutils.misc.Table` — build aligned text tables which can be exported to LaTeX with `Table.to_latex()` or Markdown with `Table.to_markdown()`.
318
327
  - `pelutils.misc.hardware_info` / `OS` — describe the machine the code runs on.
319
328
  - `pelutils.misc.git_repo_info` — the repo and commit the code is executing in.
320
329
  - Assorted file and dict helpers (`reverse_line_iterator`, `except_keys`, ...).
@@ -329,25 +338,47 @@ If no wheel matches, `pip` builds from source which requires `<Python.h>` — in
329
338
  32-bit systems are not fully supported.
330
339
  Most of the library is Python-only and should work but using any C-dependent code (namely `unique` and `SparseGridBlobDetection`) is likely to end in a segfault.
331
340
 
332
- ## Updating and releasing
341
+ ## Development
333
342
 
334
- `pelutils` uses the `master` branch as a stable development branch.
335
- The `release` branch contains the latest version on PyPI.
336
- New features should be made in feature branches from `master` that can be merged into `master` once ready.
337
- When a new release is ready, update `pelutils/__version__.py` and rebase `master` onto `release`.
338
- Then push a new tag from `release` named `vX.Y.Z`.
343
+ Use [uv](https://docs.astral.sh/uv/) for dependency management, builds, and running
344
+ development tools. Install uv, then from the repository root sync the development
345
+ environment (a C compiler and Python headers are required to build the native extension):
346
+
347
+ ```sh
348
+ uv sync --python 3.11 --group dev
349
+ ```
350
+
351
+ The `dev` dependency group includes CPU-only PyTorch and documentation tools.
352
+
353
+ Run checks and tools through `uv run`:
339
354
 
340
- When new code is merged into `master`, a number of checks are run.
341
- These can be tested locally with the following commands.
342
355
  ```sh
343
356
  # Linting and formatting
344
- ruff format pelutils tests
345
- ruff check pelutils tests
357
+ uv run --python 3.11 ruff format pelutils tests
358
+ uv run --python 3.11 ruff check pelutils tests
346
359
  # Type checking
347
- basedpyright pelutils
360
+ uv run --python 3.11 basedpyright pelutils
348
361
  # Unit tests
349
- python -m pytest tests --cov pelutils
362
+ uv run --python 3.11 pytest tests --cov pelutils
350
363
  # Build docs
351
- # Once build, open docs/build/html/index.html in your browser to see them
352
- make -C docs html
364
+ uv run --python 3.11 make -C docs html
365
+ # Preview at docs/build/html/index.html
353
366
  ```
367
+
368
+ After changing C sources, rebuild the extension with
369
+ `uv sync --python 3.11 --group dev --reinstall-package pelutils`.
370
+
371
+ The wheel-build CI tests each built wheel in a cibuildwheel environment provisioned
372
+ from the locked `dev` group with uv; pytest runs against the installed wheel. Read the
373
+ Docs also syncs that group through uv.
374
+
375
+ ## Updating and releasing
376
+
377
+ `pelutils` uses the `master` branch as a stable development branch.
378
+ The `release` branch contains the latest version on PyPI.
379
+ New features should be made in feature branches from `master` that can be merged into `master` once ready.
380
+ When a new release is ready, update `pyproject.toml` and rebase `master` onto `release`.
381
+ Then push a new tag from `release` named `vX.Y.Z`.
382
+
383
+ When new code is merged into `master`, the checks listed above are run.
384
+ To build distribution artifacts locally, use `uv build --python 3.11`.
@@ -288,7 +288,7 @@ def process(features: FloatArray, labels: IntArray, mask: BoolArray): ...
288
288
 
289
289
  ## Also included
290
290
 
291
- - `pelutils.misc.Table` — build aligned text tables which can also be easily export to LaTeX with `Table.to_latex()`.
291
+ - `pelutils.misc.Table` — build aligned text tables which can be exported to LaTeX with `Table.to_latex()` or Markdown with `Table.to_markdown()`.
292
292
  - `pelutils.misc.hardware_info` / `OS` — describe the machine the code runs on.
293
293
  - `pelutils.misc.git_repo_info` — the repo and commit the code is executing in.
294
294
  - Assorted file and dict helpers (`reverse_line_iterator`, `except_keys`, ...).
@@ -303,25 +303,47 @@ If no wheel matches, `pip` builds from source which requires `<Python.h>` — in
303
303
  32-bit systems are not fully supported.
304
304
  Most of the library is Python-only and should work but using any C-dependent code (namely `unique` and `SparseGridBlobDetection`) is likely to end in a segfault.
305
305
 
306
- ## Updating and releasing
306
+ ## Development
307
307
 
308
- `pelutils` uses the `master` branch as a stable development branch.
309
- The `release` branch contains the latest version on PyPI.
310
- New features should be made in feature branches from `master` that can be merged into `master` once ready.
311
- When a new release is ready, update `pelutils/__version__.py` and rebase `master` onto `release`.
312
- Then push a new tag from `release` named `vX.Y.Z`.
308
+ Use [uv](https://docs.astral.sh/uv/) for dependency management, builds, and running
309
+ development tools. Install uv, then from the repository root sync the development
310
+ environment (a C compiler and Python headers are required to build the native extension):
311
+
312
+ ```sh
313
+ uv sync --python 3.11 --group dev
314
+ ```
315
+
316
+ The `dev` dependency group includes CPU-only PyTorch and documentation tools.
317
+
318
+ Run checks and tools through `uv run`:
313
319
 
314
- When new code is merged into `master`, a number of checks are run.
315
- These can be tested locally with the following commands.
316
320
  ```sh
317
321
  # Linting and formatting
318
- ruff format pelutils tests
319
- ruff check pelutils tests
322
+ uv run --python 3.11 ruff format pelutils tests
323
+ uv run --python 3.11 ruff check pelutils tests
320
324
  # Type checking
321
- basedpyright pelutils
325
+ uv run --python 3.11 basedpyright pelutils
322
326
  # Unit tests
323
- python -m pytest tests --cov pelutils
327
+ uv run --python 3.11 pytest tests --cov pelutils
324
328
  # Build docs
325
- # Once build, open docs/build/html/index.html in your browser to see them
326
- make -C docs html
329
+ uv run --python 3.11 make -C docs html
330
+ # Preview at docs/build/html/index.html
327
331
  ```
332
+
333
+ After changing C sources, rebuild the extension with
334
+ `uv sync --python 3.11 --group dev --reinstall-package pelutils`.
335
+
336
+ The wheel-build CI tests each built wheel in a cibuildwheel environment provisioned
337
+ from the locked `dev` group with uv; pytest runs against the installed wheel. Read the
338
+ Docs also syncs that group through uv.
339
+
340
+ ## Updating and releasing
341
+
342
+ `pelutils` uses the `master` branch as a stable development branch.
343
+ The `release` branch contains the latest version on PyPI.
344
+ New features should be made in feature branches from `master` that can be merged into `master` once ready.
345
+ When a new release is ready, update `pyproject.toml` and rebase `master` onto `release`.
346
+ Then push a new tag from `release` named `vX.Y.Z`.
347
+
348
+ When new code is merged into `master`, the checks listed above are run.
349
+ To build distribution artifacts locally, use `uv build --python 3.11`.
@@ -0,0 +1,5 @@
1
+ from importlib.metadata import version as _get_distribution_version
2
+
3
+ __version__ = _get_distribution_version("pelutils")
4
+
5
+ __all__ = ("__version__",)
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, overload
4
+
5
+ import numpy.typing as npt
6
+
7
+ from pelutils.types import AnyArray, IntArray
8
+
9
+ @overload
10
+ def unique(
11
+ array: npt.ArrayLike,
12
+ *,
13
+ return_index: Literal[False] = False,
14
+ return_inverse: Literal[False] = False,
15
+ return_counts: Literal[False] = False,
16
+ axis: int = 0,
17
+ ) -> AnyArray: ...
18
+ @overload
19
+ def unique(
20
+ array: npt.ArrayLike,
21
+ *,
22
+ return_index: Literal[True],
23
+ return_inverse: Literal[False] = False,
24
+ return_counts: Literal[False] = False,
25
+ axis: int = 0,
26
+ ) -> tuple[AnyArray, IntArray]: ...
27
+ @overload
28
+ def unique(
29
+ array: npt.ArrayLike,
30
+ *,
31
+ return_index: Literal[False] = False,
32
+ return_inverse: Literal[True],
33
+ return_counts: Literal[False] = False,
34
+ axis: int = 0,
35
+ ) -> tuple[AnyArray, IntArray]: ...
36
+ @overload
37
+ def unique(
38
+ array: npt.ArrayLike,
39
+ *,
40
+ return_index: Literal[True],
41
+ return_inverse: Literal[True],
42
+ return_counts: Literal[False] = False,
43
+ axis: int = 0,
44
+ ) -> tuple[AnyArray, IntArray, IntArray]: ...
45
+ @overload
46
+ def unique(
47
+ array: npt.ArrayLike,
48
+ *,
49
+ return_index: Literal[False] = False,
50
+ return_inverse: Literal[False] = False,
51
+ return_counts: Literal[True],
52
+ axis: int = 0,
53
+ ) -> tuple[AnyArray, IntArray]: ...
54
+ @overload
55
+ def unique(
56
+ array: npt.ArrayLike,
57
+ *,
58
+ return_index: Literal[True],
59
+ return_inverse: Literal[False] = False,
60
+ return_counts: Literal[True],
61
+ axis: int = 0,
62
+ ) -> tuple[AnyArray, IntArray, IntArray]: ...
63
+ @overload
64
+ def unique(
65
+ array: npt.ArrayLike,
66
+ *,
67
+ return_index: Literal[False] = False,
68
+ return_inverse: Literal[True],
69
+ return_counts: Literal[True],
70
+ axis: int = 0,
71
+ ) -> tuple[AnyArray, IntArray, IntArray]: ...
72
+ @overload
73
+ def unique(
74
+ array: npt.ArrayLike,
75
+ *,
76
+ return_index: Literal[True],
77
+ return_inverse: Literal[True],
78
+ return_counts: Literal[True],
79
+ axis: int = 0,
80
+ ) -> tuple[AnyArray, IntArray, IntArray, IntArray]: ...
@@ -1,7 +1,5 @@
1
- import re
2
-
3
- from rich.color import ANSI_COLOR_NAMES
4
1
  from rich.console import Console
2
+ from rich.markup import escape
5
3
  from typing_extensions import override
6
4
 
7
5
  _stdout_console = Console(highlight=False)
@@ -14,10 +12,6 @@ class RichString:
14
12
  This allows for printing and logging without rich syntax causing issues.
15
13
  """
16
14
 
17
- _open_tag_regex = re.compile("(%s)" % "|".join(r"\[" + c + r"\]" for c in ANSI_COLOR_NAMES)) # noqa: UP031
18
- _close_tag_regex_1 = re.compile(r"\\(\[\/.*\])")
19
- _close_tag_regex_2 = re.compile(r"(\[\/.*\])")
20
-
21
15
  def __init__(self, stderr: bool = False):
22
16
  self.strings: list[str] = list() # Normal strings
23
17
  self.riches: list[str] = list() # Corresponding strings with rich syntax
@@ -26,10 +20,8 @@ class RichString:
26
20
  def add_string(self, s: str, rich: str | None = None):
27
21
  """Add a new string and optionally a rich string equivalent."""
28
22
  if rich is None:
29
- # Escape beginning brackets to prevent accidental formatting when printing
30
- rich = re.sub(self._open_tag_regex, r"\\\1", s)
31
- rich = re.sub(self._close_tag_regex_1, r"\1", rich)
32
- rich = re.sub(self._close_tag_regex_2, r"\\\1", rich)
23
+ # Escape all markup so plain text cannot be mistaken for Rich formatting.
24
+ rich = escape(s)
33
25
  self.strings.append(s)
34
26
  self.riches.append(rich)
35
27
 
@@ -6,7 +6,7 @@ and rewriting them each time is tedious and error-prone. This module is the grab
6
6
  those: each helper is self-contained and useful on its own, so import only what you need.
7
7
 
8
8
  Main inclusions are :class:`Table` for aligned text tables that also export to LaTeX via
9
- :meth:`Table.to_latex`; :class:`OS` and :data:`hardware_info` for describing the machine
9
+ :meth:`Table.to_latex` and Markdown via :meth:`Table.to_markdown`; :class:`OS` and :data:`hardware_info` for describing the machine
10
10
  the code runs on; :func:`git_repo_info` for the repository and commit currently executing;
11
11
  :func:`array_bytes`/:func:`array_ptr` for low-level array introspection; and small file
12
12
  and dict helpers such as :func:`reverse_line_iterator` and :func:`except_keys`.
@@ -1,16 +1,17 @@
1
- import re
2
1
  from collections.abc import Sequence
3
2
  from typing import Any
4
3
 
4
+ from rich.cells import cell_len
5
5
  from typing_extensions import override
6
6
 
7
7
 
8
8
  class Table:
9
- """Build aligned, human-readable text tables that can also be exported to LaTeX.
9
+ """Build aligned, human-readable text tables that can also be exported to LaTeX and Markdown.
10
10
 
11
11
  Add an optional header, then rows, and render with ``str(table)``; column widths are
12
12
  computed automatically. :meth:`add_hline` inserts a horizontal rule and
13
- :meth:`to_latex` produces ``booktabs``-style LaTeX.
13
+ :meth:`to_latex` produces ``booktabs``-style LaTeX, and :meth:`to_markdown`
14
+ produces a Markdown pipe table.
14
15
 
15
16
  Example
16
17
  -------
@@ -67,29 +68,58 @@ class Table:
67
68
  self._hlines.add(len(self._rows) - 1)
68
69
 
69
70
  def to_latex(self) -> str:
70
- """Produce LaTeX code for the table to included in a tabular environment.
71
+ """Produce LaTeX code for the table to include in a tabular environment.
71
72
 
72
73
  It assumes the booktabs package is used.
73
74
  """
74
- formatted = str(self)
75
- lines = formatted.splitlines()
76
- lines.insert(0, r"\toprule")
75
+ assert self._width is not None
76
+ all_rows = [self._header, *self._rows] if self._header else self._rows
77
+ widths = [max(cell_len(row[j]) for row in all_rows) for j in range(self._width)]
78
+
79
+ lines = [r"\toprule"]
80
+ if self._header:
81
+ header = (self._format_element(cell, width, True) for cell, width in zip(self._header, widths, strict=True))
82
+ lines.append(" & ".join(header) + r" \\")
83
+ lines.append(r"\midrule")
84
+
85
+ for i, (row, left_align) in enumerate(zip(self._rows, self._left_aligns, strict=True)):
86
+ cells = (self._format_element(cell, width, align) for cell, width, align in zip(row, widths, left_align, strict=True))
87
+ lines.append(" & ".join(cells) + r" \\")
88
+ if i in self._hlines:
89
+ lines.append(r"\midrule")
90
+
77
91
  lines.append(r"\bottomrule")
92
+ return "\n".join(lines)
78
93
 
79
- for i, line in enumerate(lines):
80
- if re.match(r"^(-+\+)+-+$", line):
81
- lines[i] = r"\midrule"
82
- elif re.match(r"^(.+\|)+.+$", line):
83
- lines[i] = line.replace("|", "&") + r" \\"
94
+ def to_markdown(self) -> str:
95
+ """Produce a Markdown pipe table.
84
96
 
97
+ Markdown requires a header row, so a table without an explicit header gets a
98
+ blank one. Column alignment follows the first data row (or is left-aligned
99
+ when there are no data rows). Markdown has no syntax for horizontal rules
100
+ between body rows, so markers added with :meth:`add_hline` are omitted.
101
+ """
102
+ assert self._width is not None
103
+ header = self._header or ("",) * self._width
104
+ aligns = self._left_aligns[0] if self._left_aligns else (True,) * self._width
105
+ lines = [
106
+ "| " + " | ".join(self._escape_markdown_cell(cell) for cell in header) + " |",
107
+ "| " + " | ".join(":---" if align else "---:" for align in aligns) + " |",
108
+ ]
109
+ lines.extend("| " + " | ".join(self._escape_markdown_cell(cell) for cell in row) + " |" for row in self._rows)
85
110
  return "\n".join(lines)
86
111
 
112
+ @staticmethod
113
+ def _escape_markdown_cell(cell: str) -> str:
114
+ return cell.replace("\\", "\\\\").replace("|", r"\|").replace("\r\n", "\n").replace("\r", "\n").replace("\n", "<br>")
115
+
87
116
  @staticmethod
88
117
  def _format_element(element: str, width: int, left_align: bool) -> str:
118
+ padding = width - cell_len(element)
89
119
  if left_align:
90
- return element + " " * (width - len(element))
120
+ return element + " " * padding
91
121
  else:
92
- return " " * (width - len(element)) + element
122
+ return " " * padding + element
93
123
 
94
124
  @override
95
125
  def __repr__(self) -> str:
@@ -99,7 +129,7 @@ class Table:
99
129
  def __str__(self) -> str:
100
130
  assert self._width is not None
101
131
  all_rows = [self._header, *self._rows] if self._header else self._rows
102
- widths = [max(len(all_rows[i][j]) for i in range(len(all_rows))) for j in range(self._width)]
132
+ widths = [max(cell_len(all_rows[i][j]) for i in range(len(all_rows))) for j in range(self._width)]
103
133
  hline = "+".join("-" * (width + 1 + (0 < i < self._width - 1)) for i, width in enumerate(widths))
104
134
  strs = list()
105
135
  if self._header:
@@ -22,15 +22,24 @@ class UniversalJsonModel(BaseModel):
22
22
 
23
23
  model_config = ConfigDict(arbitrary_types_allowed=True)
24
24
 
25
- def to_json_dict(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
26
- """Return a JSON-compatible dictionary, pickle-encoding unsupported values."""
27
- return self.model_dump(mode="json", fallback=pickle_encode)
25
+ def to_json_dict(self, **model_dump_kwargs: Any) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
26
+ """Return a JSON-compatible dictionary, pickle-encoding unsupported values.
27
+
28
+ Keyword arguments are forwarded to :meth:`pydantic.BaseModel.model_dump`, except
29
+ ``mode`` and ``fallback``, which are fixed to preserve this method's JSON format.
30
+ """
31
+ if {"mode", "fallback"} & model_dump_kwargs.keys():
32
+ raise TypeError("'mode' and 'fallback' are fixed by to_json_dict()")
33
+ return self.model_dump(mode="json", fallback=pickle_encode, **model_dump_kwargs)
28
34
 
29
35
  @classmethod
30
- def from_json_dict(cls: type[Self], json_dict: dict[str, Any]) -> Self: # pyright: ignore[reportExplicitAny]
31
- """Build a model from :meth:`to_json_dict` output. Do not load untrusted data."""
36
+ def from_json_dict(cls: type[Self], json_dict: dict[str, Any], **model_validate_kwargs: Any) -> Self: # pyright: ignore[reportExplicitAny]
37
+ """Build a model from :meth:`to_json_dict` output. Do not load untrusted data.
38
+
39
+ Keyword arguments are forwarded to :meth:`pydantic.BaseModel.model_validate`.
40
+ """
32
41
  self_dict = from_safe_json(json_dict)
33
- return cls.model_validate(self_dict)
42
+ return cls.model_validate(self_dict, **model_validate_kwargs)
34
43
 
35
44
  def save(self, path: str | Path, *, max_line_length: int = 140, indent: int = 2, encoding: str = "utf-8"):
36
45
  """Save the model to ``path`` as human-readable JSON, creating parent directories as needed."""
@@ -347,13 +347,23 @@ class TickTock:
347
347
  profile.parent.children.remove(profile)
348
348
 
349
349
  def reset(self):
350
- """Stop all timing and profiling and clear all profiles and measurements."""
350
+ """Stop all timing and profiling and clear all profiles and measurements.
351
+
352
+ Resetting is allowed while this instance is registered by :meth:`as_active`; the
353
+ active registration remains until that context exits. Resetting while a profile
354
+ context is in progress raises :class:`TickTockException` without changing state.
355
+ """
351
356
  if self._profile_stack:
352
357
  raise TickTockException("Cannot reset TickTock while profiling is active")
353
358
  self.__init__()
354
359
 
355
360
  def reset_profiles(self):
356
- """Similar to ``reset`` but only reset profiles, leaving ``tick``/``tock`` timers intact."""
361
+ """Reset profiles while leaving ``tick``/``tock`` timers intact.
362
+
363
+ The same restrictions as :meth:`reset` apply: this is allowed while registered
364
+ by :meth:`as_active`, but raises :class:`TickTockException` during a profile
365
+ context.
366
+ """
357
367
  tick_starts = self._tick_starts
358
368
  self.reset()
359
369
  self._tick_starts = tick_starts
@@ -466,6 +476,9 @@ class TickTock:
466
476
  """Set the ``TickTock`` instance as the active instance in the current thread.
467
477
 
468
478
  It can then be retrieved with :func:`get_active_ticktock`.
479
+ Calling :meth:`reset` or :meth:`reset_profiles` inside this context does not
480
+ unset the active instance. Those methods still raise if a profile context is
481
+ currently in progress.
469
482
  """
470
483
  current_thread_id = _get_thread_uid()
471
484
  with _lock:
@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pelutils
3
- Version: 4.4.0
3
+ Version: 4.5.0
4
4
  Summary: The Swiss army knife of Python projects
5
- Home-page: https://github.com/peleiden/pelutils
6
- Download-URL: https://pypi.org/project/pelutils/
7
- Author: Asger Laurits Schultz, Søren Winkel Holm
8
- Author-email: asger.s@protonmail.com, swholm@protonmail.com
9
- License: MIT
5
+ Author-email: Asger Laurits Schultz <asger.s@protonmail.com>, Søren Winkel Holm <swholm@protonmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/peleiden/pelutils
8
+ Project-URL: Download, https://pypi.org/project/pelutils/
10
9
  Keywords: utility,logger,parser,profiling,plotting
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
@@ -20,9 +19,19 @@ Classifier: Programming Language :: Python :: 3.14
20
19
  Classifier: Programming Language :: Python :: 3.15
21
20
  Requires-Python: >=3.11
22
21
  Description-Content-Type: text/markdown
23
- Provides-Extra: dev
24
- Provides-Extra: docs
22
+ License-File: LICENSE.txt
25
23
  License-File: pelutils/_c/hashmap/LICENSE
24
+ Requires-Dist: numpy>=2.0
25
+ Requires-Dist: gitpython>=3.1.0
26
+ Requires-Dist: rich>=10.0.0
27
+ Requires-Dist: py-cpuinfo>=8.0.0
28
+ Requires-Dist: psutil>=5.8.0
29
+ Requires-Dist: matplotlib>=3.3
30
+ Requires-Dist: scipy>=1.6
31
+ Requires-Dist: tqdm>=4.55
32
+ Requires-Dist: pydantic>=2
33
+ Requires-Dist: typing_extensions>=4.6
34
+ Dynamic: license-file
26
35
 
27
36
  # pelutils
28
37
 
@@ -314,7 +323,7 @@ def process(features: FloatArray, labels: IntArray, mask: BoolArray): ...
314
323
 
315
324
  ## Also included
316
325
 
317
- - `pelutils.misc.Table` — build aligned text tables which can also be easily export to LaTeX with `Table.to_latex()`.
326
+ - `pelutils.misc.Table` — build aligned text tables which can be exported to LaTeX with `Table.to_latex()` or Markdown with `Table.to_markdown()`.
318
327
  - `pelutils.misc.hardware_info` / `OS` — describe the machine the code runs on.
319
328
  - `pelutils.misc.git_repo_info` — the repo and commit the code is executing in.
320
329
  - Assorted file and dict helpers (`reverse_line_iterator`, `except_keys`, ...).
@@ -329,25 +338,47 @@ If no wheel matches, `pip` builds from source which requires `<Python.h>` — in
329
338
  32-bit systems are not fully supported.
330
339
  Most of the library is Python-only and should work but using any C-dependent code (namely `unique` and `SparseGridBlobDetection`) is likely to end in a segfault.
331
340
 
332
- ## Updating and releasing
341
+ ## Development
333
342
 
334
- `pelutils` uses the `master` branch as a stable development branch.
335
- The `release` branch contains the latest version on PyPI.
336
- New features should be made in feature branches from `master` that can be merged into `master` once ready.
337
- When a new release is ready, update `pelutils/__version__.py` and rebase `master` onto `release`.
338
- Then push a new tag from `release` named `vX.Y.Z`.
343
+ Use [uv](https://docs.astral.sh/uv/) for dependency management, builds, and running
344
+ development tools. Install uv, then from the repository root sync the development
345
+ environment (a C compiler and Python headers are required to build the native extension):
346
+
347
+ ```sh
348
+ uv sync --python 3.11 --group dev
349
+ ```
350
+
351
+ The `dev` dependency group includes CPU-only PyTorch and documentation tools.
352
+
353
+ Run checks and tools through `uv run`:
339
354
 
340
- When new code is merged into `master`, a number of checks are run.
341
- These can be tested locally with the following commands.
342
355
  ```sh
343
356
  # Linting and formatting
344
- ruff format pelutils tests
345
- ruff check pelutils tests
357
+ uv run --python 3.11 ruff format pelutils tests
358
+ uv run --python 3.11 ruff check pelutils tests
346
359
  # Type checking
347
- basedpyright pelutils
360
+ uv run --python 3.11 basedpyright pelutils
348
361
  # Unit tests
349
- python -m pytest tests --cov pelutils
362
+ uv run --python 3.11 pytest tests --cov pelutils
350
363
  # Build docs
351
- # Once build, open docs/build/html/index.html in your browser to see them
352
- make -C docs html
364
+ uv run --python 3.11 make -C docs html
365
+ # Preview at docs/build/html/index.html
353
366
  ```
367
+
368
+ After changing C sources, rebuild the extension with
369
+ `uv sync --python 3.11 --group dev --reinstall-package pelutils`.
370
+
371
+ The wheel-build CI tests each built wheel in a cibuildwheel environment provisioned
372
+ from the locked `dev` group with uv; pytest runs against the installed wheel. Read the
373
+ Docs also syncs that group through uv.
374
+
375
+ ## Updating and releasing
376
+
377
+ `pelutils` uses the `master` branch as a stable development branch.
378
+ The `release` branch contains the latest version on PyPI.
379
+ New features should be made in feature branches from `master` that can be merged into `master` once ready.
380
+ When a new release is ready, update `pyproject.toml` and rebase `master` onto `release`.
381
+ Then push a new tag from `release` named `vX.Y.Z`.
382
+
383
+ When new code is merged into `master`, the checks listed above are run.
384
+ To build distribution artifacts locally, use `uv build --python 3.11`.
@@ -1,8 +1,9 @@
1
+ LICENSE.txt
1
2
  MANIFEST.in
2
3
  README.md
4
+ pyproject.toml
3
5
  setup.py
4
6
  pelutils/__init__.py
5
- pelutils/__version__.py
6
7
  pelutils/py.typed
7
8
  pelutils.egg-info/PKG-INFO
8
9
  pelutils.egg-info/SOURCES.txt
@@ -27,6 +28,7 @@ pelutils/_entry_points/linecounter.py
27
28
  pelutils/array/__init__.py
28
29
  pelutils/array/_blob.py
29
30
  pelutils/array/_unique.py
31
+ pelutils/array/_unique.pyi
30
32
  pelutils/array/_utils.py
31
33
  pelutils/job_parser/__init__.py
32
34
  pelutils/job_parser/_job_parser.py
@@ -0,0 +1,10 @@
1
+ numpy>=2.0
2
+ gitpython>=3.1.0
3
+ rich>=10.0.0
4
+ py-cpuinfo>=8.0.0
5
+ psutil>=5.8.0
6
+ matplotlib>=3.3
7
+ scipy>=1.6
8
+ tqdm>=4.55
9
+ pydantic>=2
10
+ typing_extensions>=4.6
@@ -0,0 +1,86 @@
1
+ # uv_build does not support the native C extension; uv uses this PEP 517 backend.
2
+ [build-system]
3
+ requires = ["setuptools>=77", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "pelutils"
8
+ version = "4.5.0"
9
+ description = "The Swiss army knife of Python projects"
10
+ readme = "README.md"
11
+ requires-python = ">=3.11"
12
+ license = "MIT"
13
+ license-files = ["LICENSE.txt", "pelutils/_c/hashmap/LICENSE"]
14
+ authors = [
15
+ { name = "Asger Laurits Schultz", email = "asger.s@protonmail.com" },
16
+ { name = "Søren Winkel Holm", email = "swholm@protonmail.com" },
17
+ ]
18
+ keywords = ["utility", "logger", "parser", "profiling", "plotting"]
19
+ classifiers = [
20
+ "Intended Audience :: Developers",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: Free Threading :: 3 - Stable",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Programming Language :: Python :: 3.15",
30
+ ]
31
+ dependencies = [
32
+ "numpy>=2.0",
33
+ "gitpython>=3.1.0",
34
+ "rich>=10.0.0",
35
+ "py-cpuinfo>=8.0.0",
36
+ "psutil>=5.8.0",
37
+ "matplotlib>=3.3",
38
+ "scipy>=1.6",
39
+ "tqdm>=4.55",
40
+ "pydantic>=2",
41
+ "typing_extensions>=4.6",
42
+ ]
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "torch>=2",
47
+ "pytest==8.4.2",
48
+ "pytest-cov==7.0.0",
49
+ "coveralls>=4.0.0",
50
+ "coverage==7.10.7",
51
+ "wheel",
52
+ "setuptools>=60.0.0",
53
+ "ruff==0.15.8",
54
+ "basedpyright==1.39.0",
55
+ "freezegun>=1.5",
56
+ "ipdb",
57
+ "sphinx",
58
+ "sphinx-autobuild",
59
+ "furo",
60
+ "myst-parser",
61
+ ]
62
+
63
+ [project.scripts]
64
+ linecounter = "pelutils._entry_points.linecounter:run"
65
+
66
+ [project.urls]
67
+ Homepage = "https://github.com/peleiden/pelutils"
68
+ Download = "https://pypi.org/project/pelutils/"
69
+
70
+ [tool.setuptools]
71
+ include-package-data = true
72
+
73
+ [tool.setuptools.packages.find]
74
+ exclude = ["tests", "tests.*"]
75
+ namespaces = false
76
+
77
+ [tool.setuptools.package-data]
78
+ pelutils = ["py.typed"]
79
+
80
+ [[tool.uv.index]]
81
+ name = "pytorch"
82
+ url = "https://download.pytorch.org/whl/cpu"
83
+ explicit = true
84
+
85
+ [tool.uv.sources]
86
+ torch = { index = "pytorch" }
@@ -0,0 +1,17 @@
1
+ import os
2
+ import sys
3
+
4
+ from setuptools import setup
5
+ from setuptools.extension import Extension
6
+
7
+ c_files = [os.path.join(root, filename) for root, _, files in os.walk("pelutils/_c") for filename in files if filename.endswith(".c")]
8
+
9
+ setup(
10
+ ext_modules=[
11
+ Extension(
12
+ name="_pelutils_c",
13
+ sources=c_files,
14
+ extra_compile_args=["-DMS_WIN64"] if sys.platform == "win32" else [],
15
+ )
16
+ ]
17
+ )
@@ -1,3 +0,0 @@
1
- from .__version__ import __version__
2
-
3
- __all__ = ("__version__",)
@@ -1,2 +0,0 @@
1
- # Do not put anything else in this file
2
- __version__ = "4.4.0"
@@ -1,33 +0,0 @@
1
- gitpython>=3.1.0
2
- matplotlib>=3.3
3
- numpy>=2.0
4
- psutil>=5.8.0
5
- py-cpuinfo>=8.0.0
6
- pydantic>=2
7
- rich>=10.0.0
8
- scipy>=1.6
9
- tqdm>=4.55
10
- typing_extensions>=4.6
11
-
12
- [dev]
13
- basedpyright==1.39.0
14
- coverage==7.10.7
15
- coveralls>=4.0.0
16
- freezegun>=1.5
17
- furo
18
- ipdb
19
- myst-parser
20
- pytest-cov==7.0.0
21
- pytest==8.4.2
22
- ruff==0.15.8
23
- setuptools>=60.0.0
24
- sphinx
25
- sphinx-autobuild
26
- torch>=2
27
- wheel
28
-
29
- [docs]
30
- furo
31
- myst-parser
32
- sphinx
33
- sphinx-autobuild
pelutils-4.4.0/setup.py DELETED
@@ -1,95 +0,0 @@
1
- import os
2
- import sys
3
- from glob import glob as glob # glob
4
-
5
- from setuptools import find_packages, setup
6
- from setuptools.extension import Extension
7
-
8
- sys.path.append("pelutils")
9
- from __version__ import __version__
10
-
11
- requirements = [
12
- "numpy>=2.0",
13
- "gitpython>=3.1.0",
14
- "rich>=10.0.0",
15
- "py-cpuinfo>=8.0.0",
16
- "psutil>=5.8.0",
17
- "matplotlib>=3.3",
18
- "scipy>=1.6",
19
- "tqdm>=4.55",
20
- "pydantic>=2",
21
- "typing_extensions>=4.6",
22
- ]
23
- requirements_dev = [
24
- "torch>=2",
25
- "pytest==8.4.2",
26
- "pytest-cov==7.0.0",
27
- "coveralls>=4.0.0",
28
- "coverage==7.10.7",
29
- "wheel",
30
- "setuptools>=60.0.0",
31
- "ruff==0.15.8",
32
- "basedpyright==1.39.0",
33
- "freezegun>=1.5",
34
- "ipdb",
35
- ]
36
- requirements_docs = [
37
- "sphinx",
38
- "sphinx-autobuild",
39
- "furo",
40
- "myst-parser",
41
- ]
42
-
43
- with open("README.md") as readme_file:
44
- README = readme_file.read()
45
-
46
- c_files = list()
47
- for root, __, files in os.walk("pelutils/_c"):
48
- c_files += [os.path.join(root, f) for f in files if f.endswith(".c")]
49
-
50
- setup_args = dict(
51
- name="pelutils",
52
- version=__version__,
53
- description="The Swiss army knife of Python projects",
54
- long_description_content_type="text/markdown",
55
- long_description=README,
56
- license="MIT",
57
- packages=find_packages(exclude=("tests", "tests.*")),
58
- package_data={"pelutils": ["py.typed"]},
59
- author="Asger Laurits Schultz, Søren Winkel Holm",
60
- author_email="asger.s@protonmail.com, swholm@protonmail.com",
61
- keywords=["utility", "logger", "parser", "profiling", "plotting"],
62
- url="https://github.com/peleiden/pelutils",
63
- download_url="https://pypi.org/project/pelutils/",
64
- install_requires=requirements,
65
- extras_require={"docs": requirements_docs, "dev": requirements_dev + requirements_docs},
66
- entry_points={
67
- "console_scripts": [
68
- "linecounter = pelutils._entry_points.linecounter:run",
69
- ]
70
- },
71
- ext_modules=[
72
- Extension(
73
- name="_pelutils_c",
74
- sources=c_files,
75
- extra_compile_args=["-DMS_WIN64"] if sys.platform == "win32" else [],
76
- )
77
- ],
78
- license_files=[os.path.join("pelutils", "_c", "hashmap", "LICENSE")],
79
- python_requires=">=3.11",
80
- classifiers=[
81
- "Intended Audience :: Developers",
82
- "Operating System :: OS Independent",
83
- "Programming Language :: Python",
84
- "Programming Language :: Python :: 3 :: Only",
85
- "Programming Language :: Python :: Free Threading :: 3 - Stable",
86
- "Programming Language :: Python :: 3.11",
87
- "Programming Language :: Python :: 3.12",
88
- "Programming Language :: Python :: 3.13",
89
- "Programming Language :: Python :: 3.14",
90
- "Programming Language :: Python :: 3.15",
91
- ],
92
- )
93
-
94
- if __name__ == "__main__":
95
- setup(**setup_args)
File without changes
File without changes
File without changes
File without changes
File without changes