pyutilkit 0.5.0__tar.gz → 0.7.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 (6) hide show
  1. LICENSE.md +11 -0
  2. PKG-INFO +8 -2
  3. docs/README.md +4 -0
  4. pyproject.toml +23 -17
  5. src/pyutilkit/term.py +8 -2
  6. src/pyutilkit/timing.py +23 -17
LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ # BSD 3-Clause License
2
+
3
+ Copyright © 2024 Stephanos Kuma.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyutilkit
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: python's missing batteries
5
5
  Home-page: https://pyutilkit.readthedocs.io/en/stable/
6
6
  License: BSD-3-Clause
@@ -9,8 +9,10 @@ Author: Stephanos Kuma
9
9
  Author-email: "Stephanos Kuma" <stephanos@kuma.ai>
10
10
  Requires-Python: >=3.9
11
11
  Classifier: Development Status :: 4 - Beta
12
+ Classifier: License :: OSI Approved :: BSD License
12
13
  Classifier: Operating System :: OS Independent
13
- Requires-Dist: pyutilkit (~=0.4)
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Requires-Dist: tzdata ; os_name == 'nt'
14
16
  Project-URL: Documentation, https://pyutilkit.readthedocs.io/en/stable/
15
17
  Project-URL: Repository, https://github.com/spapanik/pyutilkit
16
18
  Description-Content-Type: text/markdown
@@ -36,6 +38,10 @@ hopes to stop this repetition.
36
38
  - [Documentation]
37
39
  - [Changelog]
38
40
 
41
+ [build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
42
+ [build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
43
+ [lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
44
+ [lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
39
45
  [test_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
40
46
  [test_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
41
47
  [licence_badge]: https://img.shields.io/pypi/l/pyutilkit
docs/README.md CHANGED
@@ -19,6 +19,10 @@ hopes to stop this repetition.
19
19
  - [Documentation]
20
20
  - [Changelog]
21
21
 
22
+ [build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
23
+ [build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
24
+ [lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
25
+ [lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
22
26
  [test_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
23
27
  [test_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
24
28
  [licence_badge]: https://img.shields.io/pypi/l/pyutilkit
pyproject.toml CHANGED
@@ -1,12 +1,12 @@
1
1
  [build-system]
2
2
  requires = [
3
- "phosphorus>=0.4",
3
+ "phosphorus>=0.8",
4
4
  ]
5
5
  build-backend = "phosphorus.construction.api"
6
6
 
7
7
  [project]
8
8
  name = "pyutilkit"
9
- version = "0.5.0"
9
+ version = "0.7.0"
10
10
 
11
11
  authors = [
12
12
  { name = "Stephanos Kuma", email = "stephanos@kuma.ai" },
@@ -21,11 +21,13 @@ keywords = [
21
21
  classifiers = [
22
22
  "Development Status :: 4 - Beta",
23
23
  "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "License :: OSI Approved :: BSD License",
24
26
  ]
25
27
 
26
28
  requires-python = ">=3.9"
27
29
  dependencies = [
28
- "pyutilkit~=0.4",
30
+ "tzdata; os_name == 'nt'",
29
31
  ]
30
32
 
31
33
  [project.urls]
@@ -35,26 +37,25 @@ documentation = "https://pyutilkit.readthedocs.io/en/stable/"
35
37
 
36
38
  [tool.phosphorus.dev-dependencies]
37
39
  dev = [
38
- "ipdb~=0.13; python_version>='3.10'",
39
- "ipython~=8.21; python_version>='3.10'",
40
+ "ipdb~=0.13",
41
+ "ipython~=8.18",
40
42
  ]
41
43
  lint = [
42
- "black~=24.1",
43
- "mypy~=1.8",
44
- "ruff~=0.5",
45
- "typing_extensions~=4.11",
44
+ "black~=24.10",
45
+ "mypy~=1.13",
46
+ "ruff~=0.7",
46
47
  ]
47
48
  test = [
48
49
  "freezegun~=1.5",
49
- "pytest~=8.0",
50
- "pytest-cov~=5.0",
50
+ "pytest~=8.3",
51
+ "pytest-cov~=6.0",
51
52
  ]
52
53
  docs = [
53
54
  "mkdocs~=1.6",
54
55
  "mkdocs-material~=9.5",
55
56
  "mkdocs-material-extensions~=1.3",
56
- "Pygments~=2.17",
57
- "pymdown-extensions~=10.8",
57
+ "pygments~=2.18",
58
+ "pymdown-extensions~=10.12",
58
59
  ]
59
60
 
60
61
  [tool.black]
@@ -118,11 +119,10 @@ select = [
118
119
  "PGH",
119
120
  "PERF",
120
121
  "PIE",
121
- "PLC",
122
- "PLE",
123
- "PLW",
122
+ "PL",
124
123
  "PT",
125
124
  "PTH",
125
+ "PYI",
126
126
  "Q",
127
127
  "RET",
128
128
  "RSE",
@@ -147,6 +147,7 @@ ignore = [
147
147
  "COM812",
148
148
  "E501",
149
149
  "FIX002",
150
+ "PLR09",
150
151
  "TD002",
151
152
  "TD003",
152
153
  "TRY003",
@@ -155,6 +156,7 @@ ignore = [
155
156
  [tool.ruff.lint.per-file-ignores]
156
157
  "tests/**" = [
157
158
  "FBT001",
159
+ "PLR2004",
158
160
  "PT011",
159
161
  "S101",
160
162
  ]
@@ -174,16 +176,20 @@ forced-separate = [
174
176
  split-on-trailing-comma = false
175
177
 
176
178
  [tool.pytest.ini_options]
177
- addopts = "-vv"
179
+ addopts = "-ra -v --cov"
178
180
  testpaths = "tests"
179
181
 
180
182
  [tool.coverage.run]
183
+ branch = true
181
184
  source = [
182
185
  "src/",
183
186
  ]
184
187
  data_file = ".cov_cache/coverage.dat"
185
188
 
186
189
  [tool.coverage.report]
190
+ exclude_also = [
191
+ "if TYPE_CHECKING:",
192
+ ]
187
193
  show_missing = true
188
194
  skip_covered = true
189
195
  skip_empty = true
src/pyutilkit/term.py CHANGED
@@ -83,7 +83,7 @@ class SGRString(str):
83
83
  raise AttributeError(msg)
84
84
 
85
85
  def __str__(self) -> str:
86
- if not self._sgr:
86
+ if not self._sgr or os.getenv("USE_SGR_CODES") in {"0", "false", "no"}:
87
87
  return self._string
88
88
  prefix = "".join(code.sequence for code in self._sgr)
89
89
  return f"{prefix}{self._string}{SGRCodes.RESET.sequence}"
@@ -106,7 +106,13 @@ class SGRString(str):
106
106
  right_spaces: int = 1,
107
107
  space: str = " ",
108
108
  ) -> None:
109
- columns = os.get_terminal_size().columns
109
+ try:
110
+ # in pseudo-terminals it throws OSError
111
+ terminal_size = os.get_terminal_size()
112
+ except OSError:
113
+ columns = 80
114
+ else:
115
+ columns = terminal_size.columns
110
116
  text = f"{space * left_spaces}{self}{space * right_spaces}"
111
117
  title_length = left_spaces + len(self) + right_spaces
112
118
  if title_length >= columns:
src/pyutilkit/timing.py CHANGED
@@ -9,6 +9,12 @@ if TYPE_CHECKING:
9
9
 
10
10
  from typing_extensions import Self # py3.10: import Self from typing
11
11
 
12
+ METRIC_MULTIPLIER = 1000
13
+ SECONDS_PER_MINUTE = 60
14
+ MINUTES_PER_HOUR = 60
15
+ HOURS_PER_DAY = 24
16
+ SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY
17
+
12
18
 
13
19
  @dataclass(frozen=True, order=True)
14
20
  class Timing:
@@ -27,31 +33,31 @@ class Timing:
27
33
  ) -> None:
28
34
  total_nanoseconds = (
29
35
  nanoseconds
30
- + 1000 * microseconds
31
- + 1_000_000 * milliseconds
32
- + 1_000_000_000 * seconds
33
- + 86_400_000_000_000 * days
36
+ + METRIC_MULTIPLIER * microseconds
37
+ + METRIC_MULTIPLIER**2 * milliseconds
38
+ + METRIC_MULTIPLIER**3 * seconds
39
+ + SECONDS_PER_DAY * METRIC_MULTIPLIER**3 * days
34
40
  )
35
41
  object.__setattr__(self, "nanoseconds", total_nanoseconds)
36
42
 
37
43
  def __str__(self) -> str:
38
- if self.nanoseconds < 1000:
44
+ if self.nanoseconds < METRIC_MULTIPLIER:
39
45
  return f"{self.nanoseconds}ns"
40
- microseconds = self.nanoseconds / 1000
41
- if microseconds < 1000:
46
+ microseconds = self.nanoseconds / METRIC_MULTIPLIER
47
+ if microseconds < METRIC_MULTIPLIER:
42
48
  return f"{microseconds:.1f}µs"
43
- milliseconds = microseconds / 1000
44
- if milliseconds < 1000:
49
+ milliseconds = microseconds / METRIC_MULTIPLIER
50
+ if milliseconds < METRIC_MULTIPLIER:
45
51
  return f"{milliseconds:.1f}ms"
46
- seconds = milliseconds / 1000
47
- if seconds < 60:
52
+ seconds = milliseconds / METRIC_MULTIPLIER
53
+ if seconds < SECONDS_PER_MINUTE:
48
54
  return f"{seconds:.2f}s"
49
55
  round_seconds = int(seconds)
50
- minutes, seconds = divmod(round_seconds, 60)
51
- hours, minutes = divmod(minutes, 60)
52
- if hours < 24:
56
+ minutes, seconds = divmod(round_seconds, SECONDS_PER_MINUTE)
57
+ hours, minutes = divmod(minutes, MINUTES_PER_HOUR)
58
+ if hours < HOURS_PER_DAY:
53
59
  return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
54
- days, hours = divmod(hours, 24)
60
+ days, hours = divmod(hours, HOURS_PER_DAY)
55
61
  return f"{days:,}d {hours:02d}:{minutes:02d}:{seconds:02d}"
56
62
 
57
63
  def __bool__(self) -> bool:
@@ -102,8 +108,8 @@ class Stopwatch:
102
108
 
103
109
  def __exit__(
104
110
  self,
105
- exc_type: type[Exception] | None,
106
- exc_value: Exception | None,
111
+ exc_type: type[BaseException] | None,
112
+ exc_value: BaseException | None,
107
113
  traceback: TracebackType | None,
108
114
  ) -> None:
109
115
  _end = perf_counter_ns()