pyutilkit 0.10.0__tar.gz → 0.11.0.post1__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.
- pyutilkit-0.11.0.post1/PKG-INFO +136 -0
- pyutilkit-0.11.0.post1/docs/README.md +117 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/pyproject.toml +42 -27
- pyutilkit-0.11.0.post1/src/pyutilkit/__version__.py +3 -0
- pyutilkit-0.11.0.post1/src/pyutilkit/classes.py +26 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/date_utils.py +1 -1
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/files.py +3 -6
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/term.py +4 -13
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/timing.py +52 -17
- pyutilkit-0.10.0/LICENSE.md +0 -11
- pyutilkit-0.10.0/PKG-INFO +0 -62
- pyutilkit-0.10.0/docs/README.md +0 -43
- pyutilkit-0.10.0/src/pyutilkit/classes.py +0 -18
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/__init__.py +0 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/data/__init__.py +0 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/data/timezones.py +0 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/py.typed +0 -0
- {pyutilkit-0.10.0 → pyutilkit-0.11.0.post1}/src/pyutilkit/subprocess.py +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pyutilkit
|
|
3
|
+
Version: 0.11.0.post1
|
|
4
|
+
Summary: python's missing batteries
|
|
5
|
+
Keywords: utils
|
|
6
|
+
Author: Stephanos Kuma
|
|
7
|
+
Author-email: Stephanos Kuma <stephanos@kuma.ai>
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Requires-Dist: tzdata ; os_name == 'nt'
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Project-URL: homepage, https://pyutilkit.readthedocs.io/en/stable/
|
|
16
|
+
Project-URL: repository, https://github.com/spapanik/pyutilkit
|
|
17
|
+
Project-URL: documentation, https://pyutilkit.readthedocs.io/en/stable/
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# pyutilkit: python's missing batteries
|
|
21
|
+
|
|
22
|
+
[![build][build_badge]][build_url]
|
|
23
|
+
[![lint][lint_badge]][lint_url]
|
|
24
|
+
[![tests][tests_badge]][tests_url]
|
|
25
|
+
[![license][licence_badge]][licence_url]
|
|
26
|
+
[![codecov][codecov_badge]][codecov_url]
|
|
27
|
+
[![readthedocs][readthedocs_badge]][readthedocs_url]
|
|
28
|
+
[![pypi][pypi_badge]][pypi_url]
|
|
29
|
+
[![downloads][pepy_badge]][pepy_url]
|
|
30
|
+
[![build automation: yam][yam_badge]][yam_url]
|
|
31
|
+
[![Lint: ruff][ruff_badge]][ruff_url]
|
|
32
|
+
|
|
33
|
+
Python has long maintained the philosophy of "batteries included", providing users with a rich
|
|
34
|
+
standard library that avoids the need for third-party tools for most tasks. Some packages are so
|
|
35
|
+
common they have achieved similar status to the standard library. Yet, certain utilities are
|
|
36
|
+
reimplemented across countless projects. This lightweight library, with minimal dependencies,
|
|
37
|
+
aims to eliminate that repetition.
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
Install pyutilkit:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install pyutilkit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or with uv (recommended for speed):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv pip install pyutilkit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Basic usage example:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from pyutilkit.date_utils import now
|
|
57
|
+
from pyutilkit.timing import Stopwatch
|
|
58
|
+
from pyutilkit.term import SGRString, SGRCodes
|
|
59
|
+
|
|
60
|
+
# Get current time in any timezone
|
|
61
|
+
from zoneinfo import ZoneInfo
|
|
62
|
+
tokyo_time = now(ZoneInfo("Asia/Tokyo"))
|
|
63
|
+
print(f"Current time in Tokyo: {tokyo_time}")
|
|
64
|
+
|
|
65
|
+
# Measure execution time
|
|
66
|
+
stopwatch = Stopwatch()
|
|
67
|
+
with stopwatch:
|
|
68
|
+
# Your code here
|
|
69
|
+
result = sum(range(1000000))
|
|
70
|
+
print(f"Computation took: {stopwatch.elapsed}")
|
|
71
|
+
|
|
72
|
+
# Colorful terminal output
|
|
73
|
+
success = SGRString("✓ Task completed", params=[SGRCodes.GREEN, SGRCodes.BOLD])
|
|
74
|
+
success.print()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Key Features
|
|
78
|
+
|
|
79
|
+
- **🕐 Timezone Utilities**: Robust datetime handling with cross-platform timezone support, ISO parsing (including Zulu timezone), and seamless timezone conversion
|
|
80
|
+
- **⏱️ High-Precision Timing**: Nanosecond-precision timing with human-readable formatting, lap tracking, and stopwatch functionality
|
|
81
|
+
- **🎨 Terminal Formatting**: ANSI color codes with smart TTY detection, automatic style stripping for piped output, and convenient header formatting
|
|
82
|
+
- **🛡️ Error Handling**: Elegant exception handling decorator that logs errors and returns defaults instead of raising exceptions
|
|
83
|
+
- **📁 File Utilities**: Efficient SHA-256 file hashing with buffered reading for large files
|
|
84
|
+
- **🚀 Subprocess Enhancement**: Run shell commands with real-time output streaming, automatic timing, and structured results
|
|
85
|
+
- **🔧 Design Patterns**: Thread-safe Singleton metaclass implementation
|
|
86
|
+
- **✨ Zero Dependencies**: Pure Python using only standard library (except optional tzdata on Windows)
|
|
87
|
+
- **🧪 Fully Tested**: 100% test coverage with comprehensive type annotations
|
|
88
|
+
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
- **[Installation Guide](installation.md)** - Setup instructions and requirements
|
|
92
|
+
- **[Usage Guide](usage/index.md)** - Comprehensive examples and tutorials for all modules
|
|
93
|
+
- [Classes](usage/classes.md) - Singleton pattern implementation
|
|
94
|
+
- [Date Utilities](usage/date_utils.md) - Timezone-aware datetime operations
|
|
95
|
+
- [File Utilities](usage/files.md) - Exception handling and file hashing
|
|
96
|
+
- [Subprocess](usage/subprocess.md) - Enhanced command execution
|
|
97
|
+
- [Terminal](usage/term.md) - Terminal formatting and colors
|
|
98
|
+
- [Timing](usage/timing.md) - Performance measurement and benchmarking
|
|
99
|
+
- **[Changelog](CHANGELOG.md)** - Version history and changes
|
|
100
|
+
- **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
- Python 3.10 or higher
|
|
105
|
+
- No external dependencies (tzdata is automatically installed on Windows)
|
|
106
|
+
|
|
107
|
+
## Links
|
|
108
|
+
|
|
109
|
+
- [Full Documentation](https://pyutilkit.readthedocs.io/en/stable/)
|
|
110
|
+
- [PyPI Package](https://pypi.org/project/pyutilkit)
|
|
111
|
+
- [GitHub Repository](https://github.com/spapanik/pyutilkit)
|
|
112
|
+
- [Changelog](CHANGELOG.md)
|
|
113
|
+
- [Report Issues](https://github.com/spapanik/pyutilkit/issues)
|
|
114
|
+
|
|
115
|
+
[build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
|
|
116
|
+
[build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
|
|
117
|
+
[lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
|
|
118
|
+
[lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
|
|
119
|
+
[tests_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
|
|
120
|
+
[tests_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
|
|
121
|
+
[licence_badge]: https://img.shields.io/pypi/l/pyutilkit
|
|
122
|
+
[licence_url]: https://pyutilkit.readthedocs.io/en/stable/LICENSE/
|
|
123
|
+
[codecov_badge]: https://codecov.io/github/spapanik/pyutilkit/graph/badge.svg?token=Q20F84BW72
|
|
124
|
+
[codecov_url]: https://codecov.io/github/spapanik/pyutilkit
|
|
125
|
+
[readthedocs_badge]: https://readthedocs.org/projects/pyutilkit/badge/?version=latest
|
|
126
|
+
[readthedocs_url]: https://pyutilkit.readthedocs.io/en/latest/
|
|
127
|
+
[pypi_badge]: https://img.shields.io/pypi/v/pyutilkit
|
|
128
|
+
[pypi_url]: https://pypi.org/project/pyutilkit
|
|
129
|
+
[pepy_badge]: https://pepy.tech/badge/pyutilkit
|
|
130
|
+
[pepy_url]: https://pepy.tech/project/pyutilkit
|
|
131
|
+
[yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
|
|
132
|
+
[yam_url]: https://github.com/spapanik/yamk
|
|
133
|
+
[ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
|
|
134
|
+
[ruff_url]: https://github.com/charliermarsh/ruff
|
|
135
|
+
[Documentation]: https://pyutilkit.readthedocs.io/en/stable/
|
|
136
|
+
[Changelog]: https://pyutilkit.readthedocs.io/en/stable/CHANGELOG/
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# pyutilkit: python's missing batteries
|
|
2
|
+
|
|
3
|
+
[![build][build_badge]][build_url]
|
|
4
|
+
[![lint][lint_badge]][lint_url]
|
|
5
|
+
[![tests][tests_badge]][tests_url]
|
|
6
|
+
[![license][licence_badge]][licence_url]
|
|
7
|
+
[![codecov][codecov_badge]][codecov_url]
|
|
8
|
+
[![readthedocs][readthedocs_badge]][readthedocs_url]
|
|
9
|
+
[![pypi][pypi_badge]][pypi_url]
|
|
10
|
+
[![downloads][pepy_badge]][pepy_url]
|
|
11
|
+
[![build automation: yam][yam_badge]][yam_url]
|
|
12
|
+
[![Lint: ruff][ruff_badge]][ruff_url]
|
|
13
|
+
|
|
14
|
+
Python has long maintained the philosophy of "batteries included", providing users with a rich
|
|
15
|
+
standard library that avoids the need for third-party tools for most tasks. Some packages are so
|
|
16
|
+
common they have achieved similar status to the standard library. Yet, certain utilities are
|
|
17
|
+
reimplemented across countless projects. This lightweight library, with minimal dependencies,
|
|
18
|
+
aims to eliminate that repetition.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
Install pyutilkit:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install pyutilkit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or with uv (recommended for speed):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv pip install pyutilkit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Basic usage example:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pyutilkit.date_utils import now
|
|
38
|
+
from pyutilkit.timing import Stopwatch
|
|
39
|
+
from pyutilkit.term import SGRString, SGRCodes
|
|
40
|
+
|
|
41
|
+
# Get current time in any timezone
|
|
42
|
+
from zoneinfo import ZoneInfo
|
|
43
|
+
tokyo_time = now(ZoneInfo("Asia/Tokyo"))
|
|
44
|
+
print(f"Current time in Tokyo: {tokyo_time}")
|
|
45
|
+
|
|
46
|
+
# Measure execution time
|
|
47
|
+
stopwatch = Stopwatch()
|
|
48
|
+
with stopwatch:
|
|
49
|
+
# Your code here
|
|
50
|
+
result = sum(range(1000000))
|
|
51
|
+
print(f"Computation took: {stopwatch.elapsed}")
|
|
52
|
+
|
|
53
|
+
# Colorful terminal output
|
|
54
|
+
success = SGRString("✓ Task completed", params=[SGRCodes.GREEN, SGRCodes.BOLD])
|
|
55
|
+
success.print()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Key Features
|
|
59
|
+
|
|
60
|
+
- **🕐 Timezone Utilities**: Robust datetime handling with cross-platform timezone support, ISO parsing (including Zulu timezone), and seamless timezone conversion
|
|
61
|
+
- **⏱️ High-Precision Timing**: Nanosecond-precision timing with human-readable formatting, lap tracking, and stopwatch functionality
|
|
62
|
+
- **🎨 Terminal Formatting**: ANSI color codes with smart TTY detection, automatic style stripping for piped output, and convenient header formatting
|
|
63
|
+
- **🛡️ Error Handling**: Elegant exception handling decorator that logs errors and returns defaults instead of raising exceptions
|
|
64
|
+
- **📁 File Utilities**: Efficient SHA-256 file hashing with buffered reading for large files
|
|
65
|
+
- **🚀 Subprocess Enhancement**: Run shell commands with real-time output streaming, automatic timing, and structured results
|
|
66
|
+
- **🔧 Design Patterns**: Thread-safe Singleton metaclass implementation
|
|
67
|
+
- **✨ Zero Dependencies**: Pure Python using only standard library (except optional tzdata on Windows)
|
|
68
|
+
- **🧪 Fully Tested**: 100% test coverage with comprehensive type annotations
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
- **[Installation Guide](installation.md)** - Setup instructions and requirements
|
|
73
|
+
- **[Usage Guide](usage/index.md)** - Comprehensive examples and tutorials for all modules
|
|
74
|
+
- [Classes](usage/classes.md) - Singleton pattern implementation
|
|
75
|
+
- [Date Utilities](usage/date_utils.md) - Timezone-aware datetime operations
|
|
76
|
+
- [File Utilities](usage/files.md) - Exception handling and file hashing
|
|
77
|
+
- [Subprocess](usage/subprocess.md) - Enhanced command execution
|
|
78
|
+
- [Terminal](usage/term.md) - Terminal formatting and colors
|
|
79
|
+
- [Timing](usage/timing.md) - Performance measurement and benchmarking
|
|
80
|
+
- **[Changelog](CHANGELOG.md)** - Version history and changes
|
|
81
|
+
- **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
- Python 3.10 or higher
|
|
86
|
+
- No external dependencies (tzdata is automatically installed on Windows)
|
|
87
|
+
|
|
88
|
+
## Links
|
|
89
|
+
|
|
90
|
+
- [Full Documentation](https://pyutilkit.readthedocs.io/en/stable/)
|
|
91
|
+
- [PyPI Package](https://pypi.org/project/pyutilkit)
|
|
92
|
+
- [GitHub Repository](https://github.com/spapanik/pyutilkit)
|
|
93
|
+
- [Changelog](CHANGELOG.md)
|
|
94
|
+
- [Report Issues](https://github.com/spapanik/pyutilkit/issues)
|
|
95
|
+
|
|
96
|
+
[build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
|
|
97
|
+
[build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
|
|
98
|
+
[lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
|
|
99
|
+
[lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
|
|
100
|
+
[tests_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
|
|
101
|
+
[tests_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
|
|
102
|
+
[licence_badge]: https://img.shields.io/pypi/l/pyutilkit
|
|
103
|
+
[licence_url]: https://pyutilkit.readthedocs.io/en/stable/LICENSE/
|
|
104
|
+
[codecov_badge]: https://codecov.io/github/spapanik/pyutilkit/graph/badge.svg?token=Q20F84BW72
|
|
105
|
+
[codecov_url]: https://codecov.io/github/spapanik/pyutilkit
|
|
106
|
+
[readthedocs_badge]: https://readthedocs.org/projects/pyutilkit/badge/?version=latest
|
|
107
|
+
[readthedocs_url]: https://pyutilkit.readthedocs.io/en/latest/
|
|
108
|
+
[pypi_badge]: https://img.shields.io/pypi/v/pyutilkit
|
|
109
|
+
[pypi_url]: https://pypi.org/project/pyutilkit
|
|
110
|
+
[pepy_badge]: https://pepy.tech/badge/pyutilkit
|
|
111
|
+
[pepy_url]: https://pepy.tech/project/pyutilkit
|
|
112
|
+
[yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
|
|
113
|
+
[yam_url]: https://github.com/spapanik/yamk
|
|
114
|
+
[ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
|
|
115
|
+
[ruff_url]: https://github.com/charliermarsh/ruff
|
|
116
|
+
[Documentation]: https://pyutilkit.readthedocs.io/en/stable/
|
|
117
|
+
[Changelog]: https://pyutilkit.readthedocs.io/en/stable/CHANGELOG/
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
requires = [
|
|
3
|
-
"
|
|
3
|
+
"uv_build>=0.11.0,<0.12.0",
|
|
4
4
|
]
|
|
5
|
-
build-backend = "
|
|
5
|
+
build-backend = "uv_build"
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyutilkit"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.11.0.post1"
|
|
10
10
|
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Stephanos Kuma", email = "stephanos@kuma.ai" },
|
|
@@ -22,10 +22,10 @@ classifiers = [
|
|
|
22
22
|
"Development Status :: 4 - Beta",
|
|
23
23
|
"Operating System :: OS Independent",
|
|
24
24
|
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
-
"
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
|
-
requires-python = ">=3.
|
|
28
|
+
requires-python = ">=3.10"
|
|
29
29
|
dependencies = [
|
|
30
30
|
"tzdata; os_name == 'nt'",
|
|
31
31
|
]
|
|
@@ -35,32 +35,35 @@ homepage = "https://pyutilkit.readthedocs.io/en/stable/"
|
|
|
35
35
|
repository = "https://github.com/spapanik/pyutilkit"
|
|
36
36
|
documentation = "https://pyutilkit.readthedocs.io/en/stable/"
|
|
37
37
|
|
|
38
|
-
[
|
|
38
|
+
[dependency-groups]
|
|
39
39
|
dev = [
|
|
40
40
|
"ipdb~=0.13",
|
|
41
|
-
"ipython~=8.
|
|
41
|
+
"ipython~=8.37",
|
|
42
|
+
"ptpython~=3.0",
|
|
43
|
+
{ include-group = "lint" },
|
|
44
|
+
{ include-group = "test" },
|
|
45
|
+
{ include-group = "docs" },
|
|
42
46
|
]
|
|
43
47
|
lint = [
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
48
|
+
"mypy~=1.19",
|
|
49
|
+
"ruff~=0.15",
|
|
50
|
+
"ty~=0.0.26",
|
|
51
|
+
{ include-group = "test_core" },
|
|
47
52
|
]
|
|
48
53
|
test = [
|
|
54
|
+
"pytest-cov~=7.0",
|
|
55
|
+
{ include-group = "test_core" },
|
|
56
|
+
]
|
|
57
|
+
test_core = [
|
|
49
58
|
"freezegun~=1.5",
|
|
50
|
-
"pytest~=
|
|
51
|
-
"pytest-cov~=6.0",
|
|
59
|
+
"pytest~=9.0",
|
|
52
60
|
]
|
|
53
61
|
docs = [
|
|
54
62
|
"mkdocs~=1.6",
|
|
55
|
-
"mkdocs-material~=9.
|
|
63
|
+
"mkdocs-material~=9.6",
|
|
56
64
|
"mkdocs-material-extensions~=1.3",
|
|
57
|
-
"pygments~=2.
|
|
58
|
-
"pymdown-extensions~=10.
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
[tool.black]
|
|
62
|
-
target-version = [
|
|
63
|
-
"py39",
|
|
65
|
+
"pygments~=2.19",
|
|
66
|
+
"pymdown-extensions~=10.15",
|
|
64
67
|
]
|
|
65
68
|
|
|
66
69
|
[tool.mypy]
|
|
@@ -95,7 +98,7 @@ disallow_any_decorated = false # mock.MagicMock is Any
|
|
|
95
98
|
src = [
|
|
96
99
|
"src",
|
|
97
100
|
]
|
|
98
|
-
target-version = "
|
|
101
|
+
target-version = "py310"
|
|
99
102
|
|
|
100
103
|
[tool.ruff.lint]
|
|
101
104
|
select = [
|
|
@@ -103,11 +106,10 @@ select = [
|
|
|
103
106
|
]
|
|
104
107
|
ignore = [
|
|
105
108
|
"C901", # Adding a limit to complexity is too arbitrary
|
|
106
|
-
"COM812", # Avoid
|
|
109
|
+
"COM812", # Avoid conflicts with the formatter
|
|
107
110
|
"D10", # Not everything needs a docstring
|
|
108
111
|
"D203", # Prefer `no-blank-line-before-class` (D211)
|
|
109
112
|
"D213", # Prefer `multi-line-summary-first-line` (D212)
|
|
110
|
-
"E501", # Avoid clashes with black
|
|
111
113
|
"PLR09", # Adding a limit to complexity is too arbitrary
|
|
112
114
|
]
|
|
113
115
|
|
|
@@ -116,6 +118,7 @@ ignore = [
|
|
|
116
118
|
"FBT001", # Test arguments are handled by pytest
|
|
117
119
|
"PLR2004", # Tests should contain magic number comparisons
|
|
118
120
|
"S101", # Pytest needs assert statements
|
|
121
|
+
"SLF001", # Tests need to access private members for coverage
|
|
119
122
|
]
|
|
120
123
|
|
|
121
124
|
[tool.ruff.lint.flake8-tidy-imports]
|
|
@@ -132,9 +135,16 @@ forced-separate = [
|
|
|
132
135
|
]
|
|
133
136
|
split-on-trailing-comma = false
|
|
134
137
|
|
|
135
|
-
[tool.pytest
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
[tool.pytest]
|
|
139
|
+
minversion = "9.0"
|
|
140
|
+
strict = true
|
|
141
|
+
addopts = [
|
|
142
|
+
"-ra",
|
|
143
|
+
"-v",
|
|
144
|
+
]
|
|
145
|
+
testpaths = [
|
|
146
|
+
"tests",
|
|
147
|
+
]
|
|
138
148
|
|
|
139
149
|
[tool.coverage.run]
|
|
140
150
|
branch = true
|
|
@@ -142,12 +152,17 @@ source = [
|
|
|
142
152
|
"src/",
|
|
143
153
|
]
|
|
144
154
|
data_file = ".cov_cache/coverage.dat"
|
|
155
|
+
omit = [
|
|
156
|
+
"src/**/type_defs.py",
|
|
157
|
+
]
|
|
145
158
|
|
|
146
159
|
[tool.coverage.report]
|
|
147
160
|
exclude_also = [
|
|
148
161
|
"if TYPE_CHECKING:",
|
|
162
|
+
"raise NotImplementedError",
|
|
163
|
+
"raise UnreachableCodeError",
|
|
149
164
|
]
|
|
150
|
-
fail_under =
|
|
165
|
+
fail_under = 100
|
|
151
166
|
precision = 2
|
|
152
167
|
show_missing = true
|
|
153
168
|
skip_covered = true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from typing import TypeVar, cast
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Singleton(type):
|
|
10
|
+
instance: object
|
|
11
|
+
_lock: threading.Lock
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
cls, name: str, bases: tuple[type[object], ...], namespace: dict[str, object]
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(name, bases, namespace)
|
|
17
|
+
cls.instance = None
|
|
18
|
+
cls._lock = threading.Lock()
|
|
19
|
+
|
|
20
|
+
def __call__(cls: type[_T]) -> _T:
|
|
21
|
+
mcs = cast("Singleton", cls)
|
|
22
|
+
if mcs.instance is None:
|
|
23
|
+
with mcs._lock:
|
|
24
|
+
if mcs.instance is None:
|
|
25
|
+
mcs.instance = super(Singleton, mcs).__call__()
|
|
26
|
+
return cast("_T", mcs.instance)
|
|
@@ -3,19 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import logging
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import TYPE_CHECKING, TypeVar
|
|
6
|
+
from typing import TYPE_CHECKING, ParamSpec, TypeVar
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from collections.abc import Callable
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from typing_extensions import ParamSpec # upgrade: py3.9: import from typing
|
|
13
|
-
|
|
14
|
-
P = ParamSpec("P")
|
|
15
|
-
|
|
16
12
|
logger = logging.getLogger(__name__)
|
|
17
13
|
INGEST_ERROR = "Function `%s` threw `%s` when called with args=%s and kwargs=%s"
|
|
18
14
|
R_co = TypeVar("R_co", covariant=True)
|
|
15
|
+
P = ParamSpec("P")
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
def handle_exceptions(
|
|
@@ -32,7 +29,7 @@ def handle_exceptions(
|
|
|
32
29
|
except exceptions as exc:
|
|
33
30
|
getattr(logger, log_level)(
|
|
34
31
|
INGEST_ERROR,
|
|
35
|
-
func.__name__,
|
|
32
|
+
func.__name__, # ty: ignore[unresolved-attribute]
|
|
36
33
|
exc.__class__.__name__,
|
|
37
34
|
args,
|
|
38
35
|
kwargs,
|
|
@@ -67,18 +67,8 @@ class SGRCodes(IntEnum):
|
|
|
67
67
|
return f"\033[{self.value}m"
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
@dataclass(frozen=True, order=True)
|
|
70
|
+
@dataclass(frozen=True, order=True, slots=True)
|
|
71
71
|
class SGRString:
|
|
72
|
-
__slots__ = ( # upgrade: py3.9: use __slots__ = True
|
|
73
|
-
"_force_prefix",
|
|
74
|
-
"_force_sgr",
|
|
75
|
-
"_is_error",
|
|
76
|
-
"_prefix",
|
|
77
|
-
"_sgr",
|
|
78
|
-
"_string",
|
|
79
|
-
"_suffix",
|
|
80
|
-
)
|
|
81
|
-
|
|
82
72
|
_string: str
|
|
83
73
|
_sgr: tuple[SGRCodes, ...]
|
|
84
74
|
_prefix: str
|
|
@@ -215,14 +205,14 @@ class SGRString:
|
|
|
215
205
|
type(self)(self._string, prefix=prefix, suffix=suffix, params=self._sgr).print()
|
|
216
206
|
|
|
217
207
|
|
|
218
|
-
@dataclass(frozen=True, order=True)
|
|
208
|
+
@dataclass(frozen=True, order=True, slots=True)
|
|
219
209
|
class SGROutput:
|
|
220
|
-
__slots__ = ("_strings",) # upgrade: py3.9: use __slots__ = True
|
|
221
210
|
_strings: tuple[SGRString, ...]
|
|
222
211
|
|
|
223
212
|
def __init__(
|
|
224
213
|
self,
|
|
225
214
|
strings: Iterable[object],
|
|
215
|
+
*,
|
|
226
216
|
force_prefix: bool | None = None,
|
|
227
217
|
force_sgr: bool | None = None,
|
|
228
218
|
is_error: bool | None = None,
|
|
@@ -242,6 +232,7 @@ class SGROutput:
|
|
|
242
232
|
@staticmethod
|
|
243
233
|
def _clean_string(
|
|
244
234
|
string: object,
|
|
235
|
+
*,
|
|
245
236
|
force_prefix: bool | None,
|
|
246
237
|
force_sgr: bool | None,
|
|
247
238
|
is_error: bool | None,
|
|
@@ -5,21 +5,20 @@ from time import perf_counter_ns
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import Iterator
|
|
8
9
|
from types import TracebackType
|
|
9
10
|
|
|
10
11
|
from typing_extensions import Self # upgrade: py3.10: import from typing
|
|
11
12
|
|
|
12
|
-
METRIC_MULTIPLIER =
|
|
13
|
+
METRIC_MULTIPLIER = 1_000
|
|
13
14
|
SECONDS_PER_MINUTE = 60
|
|
14
15
|
MINUTES_PER_HOUR = 60
|
|
15
16
|
HOURS_PER_DAY = 24
|
|
16
17
|
SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
@dataclass(frozen=True, order=True)
|
|
20
|
+
@dataclass(frozen=True, order=True, slots=True)
|
|
20
21
|
class Timing:
|
|
21
|
-
__slots__ = ("nanoseconds",) # upgrade: py3.9: use __slots__ = True
|
|
22
|
-
|
|
23
22
|
nanoseconds: int
|
|
24
23
|
|
|
25
24
|
def __init__(
|
|
@@ -41,28 +40,35 @@ class Timing:
|
|
|
41
40
|
object.__setattr__(self, "nanoseconds", total_nanoseconds)
|
|
42
41
|
|
|
43
42
|
def __str__(self) -> str:
|
|
44
|
-
if self.nanoseconds
|
|
45
|
-
return
|
|
46
|
-
|
|
43
|
+
if self.nanoseconds == 0:
|
|
44
|
+
return "0ns"
|
|
45
|
+
sign = "" if self.nanoseconds > 0 else "-"
|
|
46
|
+
nanoseconds = abs(self.nanoseconds)
|
|
47
|
+
if nanoseconds < METRIC_MULTIPLIER:
|
|
48
|
+
return f"{sign}{nanoseconds}ns"
|
|
49
|
+
microseconds = nanoseconds / METRIC_MULTIPLIER
|
|
47
50
|
if microseconds < METRIC_MULTIPLIER:
|
|
48
|
-
return f"{microseconds:.1f}µs"
|
|
51
|
+
return f"{sign}{microseconds:.1f}µs"
|
|
49
52
|
milliseconds = microseconds / METRIC_MULTIPLIER
|
|
50
53
|
if milliseconds < METRIC_MULTIPLIER:
|
|
51
|
-
return f"{milliseconds:.1f}ms"
|
|
54
|
+
return f"{sign}{milliseconds:.1f}ms"
|
|
52
55
|
seconds = milliseconds / METRIC_MULTIPLIER
|
|
53
56
|
if seconds < SECONDS_PER_MINUTE:
|
|
54
|
-
return f"{seconds:.2f}s"
|
|
57
|
+
return f"{sign}{seconds:.2f}s"
|
|
55
58
|
round_seconds = int(seconds)
|
|
56
59
|
minutes, seconds = divmod(round_seconds, SECONDS_PER_MINUTE)
|
|
57
60
|
hours, minutes = divmod(minutes, MINUTES_PER_HOUR)
|
|
58
61
|
if hours < HOURS_PER_DAY:
|
|
59
|
-
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
62
|
+
return f"{sign}{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
60
63
|
days, hours = divmod(hours, HOURS_PER_DAY)
|
|
61
|
-
return f"{days:,}d {hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
64
|
+
return f"{sign}{days:,}d {hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
62
65
|
|
|
63
66
|
def __bool__(self) -> bool:
|
|
64
67
|
return bool(self.nanoseconds)
|
|
65
68
|
|
|
69
|
+
def __neg__(self) -> Self:
|
|
70
|
+
return self.__class__(nanoseconds=-self.nanoseconds)
|
|
71
|
+
|
|
66
72
|
def __add__(self, other: object) -> Timing:
|
|
67
73
|
if not isinstance(other, Timing):
|
|
68
74
|
return NotImplemented
|
|
@@ -73,6 +79,16 @@ class Timing:
|
|
|
73
79
|
return NotImplemented
|
|
74
80
|
return Timing(nanoseconds=self.nanoseconds + other.nanoseconds)
|
|
75
81
|
|
|
82
|
+
def __sub__(self, other: object) -> Timing:
|
|
83
|
+
if not isinstance(other, Timing):
|
|
84
|
+
return NotImplemented
|
|
85
|
+
return Timing(nanoseconds=self.nanoseconds - other.nanoseconds)
|
|
86
|
+
|
|
87
|
+
def __rsub__(self, other: object) -> Timing:
|
|
88
|
+
if not isinstance(other, Timing):
|
|
89
|
+
return NotImplemented
|
|
90
|
+
return Timing(nanoseconds=other.nanoseconds - self.nanoseconds)
|
|
91
|
+
|
|
76
92
|
def __mul__(self, other: object) -> Timing:
|
|
77
93
|
if not isinstance(other, int):
|
|
78
94
|
return NotImplemented
|
|
@@ -97,10 +113,11 @@ class Timing:
|
|
|
97
113
|
class Stopwatch:
|
|
98
114
|
_start: int
|
|
99
115
|
laps: list[Timing]
|
|
116
|
+
__slots__ = ("_start", "_zero", "laps")
|
|
100
117
|
|
|
101
118
|
def __init__(self) -> None:
|
|
102
|
-
self._start = 0
|
|
103
119
|
self.laps: list[Timing] = []
|
|
120
|
+
self._zero = Timing()
|
|
104
121
|
|
|
105
122
|
def __enter__(self) -> Self:
|
|
106
123
|
self._start = perf_counter_ns()
|
|
@@ -112,19 +129,37 @@ class Stopwatch:
|
|
|
112
129
|
exc_value: BaseException | None,
|
|
113
130
|
traceback: TracebackType | None,
|
|
114
131
|
) -> None:
|
|
115
|
-
|
|
116
|
-
self.laps.append(Timing(nanoseconds=
|
|
132
|
+
end = perf_counter_ns()
|
|
133
|
+
self.laps.append(Timing(nanoseconds=end - self._start))
|
|
134
|
+
del self._start
|
|
117
135
|
|
|
118
136
|
def __bool__(self) -> bool:
|
|
119
137
|
return bool(self.elapsed)
|
|
120
138
|
|
|
139
|
+
def __len__(self) -> int:
|
|
140
|
+
return len(self.laps)
|
|
141
|
+
|
|
142
|
+
def __iter__(self) -> Iterator[Timing]:
|
|
143
|
+
return iter(self.laps)
|
|
144
|
+
|
|
145
|
+
def reset(self) -> None:
|
|
146
|
+
self.laps.clear()
|
|
147
|
+
|
|
121
148
|
@property
|
|
122
149
|
def elapsed(self) -> Timing:
|
|
123
|
-
return sum(self.laps,
|
|
150
|
+
return sum(self.laps, self._zero)
|
|
124
151
|
|
|
125
152
|
@property
|
|
126
153
|
def average(self) -> Timing:
|
|
127
154
|
if not self.laps:
|
|
128
155
|
msg = "No laps recorded"
|
|
129
156
|
raise ZeroDivisionError(msg)
|
|
130
|
-
return self.elapsed // len(self
|
|
157
|
+
return self.elapsed // len(self)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def min(self) -> Timing:
|
|
161
|
+
return min(self.laps)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def max(self) -> Timing:
|
|
165
|
+
return max(self.laps)
|
pyutilkit-0.10.0/LICENSE.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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.
|
pyutilkit-0.10.0/PKG-INFO
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: pyutilkit
|
|
3
|
-
Version: 0.10.0
|
|
4
|
-
Summary: python's missing batteries
|
|
5
|
-
Home-page: https://pyutilkit.readthedocs.io/en/stable/
|
|
6
|
-
License: BSD-3-Clause
|
|
7
|
-
Keywords: utils
|
|
8
|
-
Author: Stephanos Kuma
|
|
9
|
-
Author-email: "Stephanos Kuma" <stephanos@kuma.ai>
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Requires-Dist: tzdata ; os_name == 'nt'
|
|
16
|
-
Project-URL: Documentation, https://pyutilkit.readthedocs.io/en/stable/
|
|
17
|
-
Project-URL: Repository, https://github.com/spapanik/pyutilkit
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
|
|
20
|
-
# pyutilkit: python's missing batteries
|
|
21
|
-
|
|
22
|
-
[![build][build_badge]][build_url]
|
|
23
|
-
[![lint][lint_badge]][lint_url]
|
|
24
|
-
[![tests][test_badge]][test_url]
|
|
25
|
-
[![license][licence_badge]][licence_url]
|
|
26
|
-
[![pypi][pypi_badge]][pypi_url]
|
|
27
|
-
[![downloads][pepy_badge]][pepy_url]
|
|
28
|
-
[![code style: black][black_badge]][black_url]
|
|
29
|
-
[![build automation: yam][yam_badge]][yam_url]
|
|
30
|
-
[![Lint: ruff][ruff_badge]][ruff_url]
|
|
31
|
-
|
|
32
|
-
The Python has long maintained the philosophy of "batteries included", giving the user
|
|
33
|
-
a rich standard library, avoiding the need for third party tools for most work. Some packages
|
|
34
|
-
are so common, that the have a similar status to the standard library. Still, some code seems
|
|
35
|
-
to be written time and again, with every project. This small library, with minimal requirements,
|
|
36
|
-
hopes to stop this repetition.
|
|
37
|
-
|
|
38
|
-
## Links
|
|
39
|
-
|
|
40
|
-
- [Documentation]
|
|
41
|
-
- [Changelog]
|
|
42
|
-
|
|
43
|
-
[build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
|
|
44
|
-
[build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
|
|
45
|
-
[lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
|
|
46
|
-
[lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
|
|
47
|
-
[test_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
|
|
48
|
-
[test_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
|
|
49
|
-
[licence_badge]: https://img.shields.io/pypi/l/pyutilkit
|
|
50
|
-
[licence_url]: https://pyutilkit.readthedocs.io/en/stable/LICENSE/
|
|
51
|
-
[pypi_badge]: https://img.shields.io/pypi/v/pyutilkit
|
|
52
|
-
[pypi_url]: https://pypi.org/project/pyutilkit
|
|
53
|
-
[pepy_badge]: https://pepy.tech/badge/pyutilkit
|
|
54
|
-
[pepy_url]: https://pepy.tech/project/pyutilkit
|
|
55
|
-
[black_badge]: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
56
|
-
[black_url]: https://github.com/psf/black
|
|
57
|
-
[yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
|
|
58
|
-
[yam_url]: https://github.com/spapanik/yamk
|
|
59
|
-
[ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
|
|
60
|
-
[ruff_url]: https://github.com/charliermarsh/ruff
|
|
61
|
-
[Documentation]: https://pyutilkit.readthedocs.io/en/stable/
|
|
62
|
-
[Changelog]: https://pyutilkit.readthedocs.io/en/stable/CHANGELOG/
|
pyutilkit-0.10.0/docs/README.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# pyutilkit: python's missing batteries
|
|
2
|
-
|
|
3
|
-
[![build][build_badge]][build_url]
|
|
4
|
-
[![lint][lint_badge]][lint_url]
|
|
5
|
-
[![tests][test_badge]][test_url]
|
|
6
|
-
[![license][licence_badge]][licence_url]
|
|
7
|
-
[![pypi][pypi_badge]][pypi_url]
|
|
8
|
-
[![downloads][pepy_badge]][pepy_url]
|
|
9
|
-
[![code style: black][black_badge]][black_url]
|
|
10
|
-
[![build automation: yam][yam_badge]][yam_url]
|
|
11
|
-
[![Lint: ruff][ruff_badge]][ruff_url]
|
|
12
|
-
|
|
13
|
-
The Python has long maintained the philosophy of "batteries included", giving the user
|
|
14
|
-
a rich standard library, avoiding the need for third party tools for most work. Some packages
|
|
15
|
-
are so common, that the have a similar status to the standard library. Still, some code seems
|
|
16
|
-
to be written time and again, with every project. This small library, with minimal requirements,
|
|
17
|
-
hopes to stop this repetition.
|
|
18
|
-
|
|
19
|
-
## Links
|
|
20
|
-
|
|
21
|
-
- [Documentation]
|
|
22
|
-
- [Changelog]
|
|
23
|
-
|
|
24
|
-
[build_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml/badge.svg
|
|
25
|
-
[build_url]: https://github.com/spapanik/pyutilkit/actions/workflows/build.yml
|
|
26
|
-
[lint_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml/badge.svg
|
|
27
|
-
[lint_url]: https://github.com/spapanik/pyutilkit/actions/workflows/lint.yml
|
|
28
|
-
[test_badge]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml/badge.svg
|
|
29
|
-
[test_url]: https://github.com/spapanik/pyutilkit/actions/workflows/tests.yml
|
|
30
|
-
[licence_badge]: https://img.shields.io/pypi/l/pyutilkit
|
|
31
|
-
[licence_url]: https://pyutilkit.readthedocs.io/en/stable/LICENSE/
|
|
32
|
-
[pypi_badge]: https://img.shields.io/pypi/v/pyutilkit
|
|
33
|
-
[pypi_url]: https://pypi.org/project/pyutilkit
|
|
34
|
-
[pepy_badge]: https://pepy.tech/badge/pyutilkit
|
|
35
|
-
[pepy_url]: https://pepy.tech/project/pyutilkit
|
|
36
|
-
[black_badge]: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
37
|
-
[black_url]: https://github.com/psf/black
|
|
38
|
-
[yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
|
|
39
|
-
[yam_url]: https://github.com/spapanik/yamk
|
|
40
|
-
[ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
|
|
41
|
-
[ruff_url]: https://github.com/charliermarsh/ruff
|
|
42
|
-
[Documentation]: https://pyutilkit.readthedocs.io/en/stable/
|
|
43
|
-
[Changelog]: https://pyutilkit.readthedocs.io/en/stable/CHANGELOG/
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import cast
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Singleton(type):
|
|
7
|
-
instance: type[Singleton] | None
|
|
8
|
-
|
|
9
|
-
def __init__(
|
|
10
|
-
cls, name: str, bases: tuple[type[object], ...], namespace: dict[str, object]
|
|
11
|
-
) -> None:
|
|
12
|
-
super().__init__(name, bases, namespace)
|
|
13
|
-
cls.instance = None
|
|
14
|
-
|
|
15
|
-
def __call__(cls) -> type[Singleton]:
|
|
16
|
-
if cls.instance is None:
|
|
17
|
-
cls.instance = super().__call__()
|
|
18
|
-
return cast(type[Singleton], cls.instance)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|