pyutilities 2.1.2__tar.gz → 2.4.0.dev1__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.
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/PKG-INFO +17 -7
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/docs/README-PUB.md +9 -2
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/pyproject.toml +118 -71
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/__init__.py +2 -0
- pyutilities-2.4.0.dev1/src/pyutilities/cli/pyutils.py +28 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/config/configuration.py +2 -1
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/io/io_utils.py +16 -4
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/common_utils.py +10 -0
- pyutilities-2.4.0.dev1/src/pyutilities/utils/datetime_utils.py +33 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/decorators.py +32 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/string_utils.py +47 -21
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/LICENSE +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/fedit.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/config/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/defaults.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/exception.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/io/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/logging.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/sys_utils.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/lambdas.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/web/__init__.py +0 -0
- {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/web/http_client.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyutilities
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: PyUtilities :: Useful python 3 utilities library for various use cases.
|
|
3
|
+
Version: 2.4.0.dev1
|
|
4
|
+
Summary: PyUtilities :: Useful python 3+ utilities library for various use cases.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
|
-
Keywords: Python Utilities,Useful Functions,String Utilities,CLI Functions,toolset,library,System Utilities
|
|
7
|
+
Keywords: Python Utilities,Useful Functions,String Utilities,CLI Functions,toolset,library,System Utilities,Useful Decorators,IO Functions
|
|
8
8
|
Author: Dmitrii Gusev
|
|
9
9
|
Author-email: dmitry.ed.gusev@gmail.com
|
|
10
10
|
Requires-Python: >=3.10,<4
|
|
@@ -16,10 +16,13 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: License :: OSI Approved :: MIT License
|
|
20
21
|
Classifier: Intended Audience :: Developers
|
|
22
|
+
Classifier: Intended Audience :: System Administrators
|
|
21
23
|
Classifier: Topic :: Utilities
|
|
22
24
|
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Dist: click (>=8.3,<9.0)
|
|
23
26
|
Requires-Dist: fake-useragent (>=2.0,<3.0)
|
|
24
27
|
Requires-Dist: loguru (==0.7.3)
|
|
25
28
|
Requires-Dist: markdown (>=3.7,<4.0)
|
|
@@ -28,9 +31,9 @@ Requires-Dist: paramiko (==4.*)
|
|
|
28
31
|
Requires-Dist: prettytable (>=3.14,<4.0)
|
|
29
32
|
Requires-Dist: pyyaml (==6.*)
|
|
30
33
|
Requires-Dist: requests (>=2.32,<3.0)
|
|
31
|
-
Requires-Dist: urllib3 (>=2.
|
|
34
|
+
Requires-Dist: urllib3 (>=2.6,<3.0)
|
|
32
35
|
Requires-Dist: xlrd (>=2.0.1,<2.1.0)
|
|
33
|
-
Requires-Dist: xlwings (>=0.
|
|
36
|
+
Requires-Dist: xlwings (>=0.35,<1.0)
|
|
34
37
|
Requires-Dist: xlwt (==1.3.0)
|
|
35
38
|
Project-URL: Bug Tracker, https://github.com/dmitry-ed-gusev/pyutilities/issues
|
|
36
39
|
Project-URL: Documentation, https://github.com/dmitry-ed-gusev/pyutilities
|
|
@@ -40,8 +43,8 @@ Description-Content-Type: text/markdown
|
|
|
40
43
|
|
|
41
44
|
# Python Utilities Library
|
|
42
45
|
|
|
43
|
-
Version 2.
|
|
44
|
-
*Last update:
|
|
46
|
+
Version 2.2.0, 2018 - 2026, Dmitrii Gusev
|
|
47
|
+
*Last update: XX.XX.2026*
|
|
45
48
|
|
|
46
49
|
## Project Description
|
|
47
50
|
|
|
@@ -49,6 +52,13 @@ Useful utilities functions for various needs for the python 3.10+. Containing 'g
|
|
|
49
52
|
|
|
50
53
|
## Versions history
|
|
51
54
|
|
|
55
|
+
### 2.2.0
|
|
56
|
+
|
|
57
|
+
Added new functions:
|
|
58
|
+
|
|
59
|
+
- func1()
|
|
60
|
+
- func2()
|
|
61
|
+
|
|
52
62
|
### 2.1.2
|
|
53
63
|
|
|
54
64
|
Major release with complete set of useful 'general purpose' functions. Documentation 'is coming'...
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Python Utilities Library
|
|
2
2
|
|
|
3
|
-
Version 2.
|
|
4
|
-
*Last update:
|
|
3
|
+
Version 2.2.0, 2018 - 2026, Dmitrii Gusev
|
|
4
|
+
*Last update: XX.XX.2026*
|
|
5
5
|
|
|
6
6
|
## Project Description
|
|
7
7
|
|
|
@@ -9,6 +9,13 @@ Useful utilities functions for various needs for the python 3.10+. Containing 'g
|
|
|
9
9
|
|
|
10
10
|
## Versions history
|
|
11
11
|
|
|
12
|
+
### 2.2.0
|
|
13
|
+
|
|
14
|
+
Added new functions:
|
|
15
|
+
|
|
16
|
+
- func1()
|
|
17
|
+
- func2()
|
|
18
|
+
|
|
12
19
|
### 2.1.2
|
|
13
20
|
|
|
14
21
|
Major release with complete set of useful 'general purpose' functions. Documentation 'is coming'...
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# - most of modules should be typed (used mypy stubs libraries)
|
|
9
9
|
#
|
|
10
10
|
# Created: Dmitrii Gusev, 30.05.2025
|
|
11
|
-
# Modified: Dmitrii Gusev,
|
|
11
|
+
# Modified: Dmitrii Gusev, 19.04.2026
|
|
12
12
|
#
|
|
13
13
|
####################################################################################################
|
|
14
14
|
|
|
@@ -20,15 +20,18 @@ build-backend = "poetry.core.masonry.api"
|
|
|
20
20
|
# -- project / poetry utility meta-information
|
|
21
21
|
[project]
|
|
22
22
|
name = "pyutilities"
|
|
23
|
-
version = "2.
|
|
24
|
-
description = "PyUtilities :: Useful python 3 utilities library for various use cases."
|
|
23
|
+
version = "2.4.0dev1"
|
|
24
|
+
description = "PyUtilities :: Useful python 3+ utilities library for various use cases."
|
|
25
25
|
authors = [
|
|
26
26
|
{name = "Dmitrii Gusev", email = "dmitry.ed.gusev@gmail.com"},
|
|
27
27
|
]
|
|
28
28
|
license = "MIT"
|
|
29
29
|
readme = "docs/README-PUB.md"
|
|
30
30
|
requires-python = ">=3.10,<4"
|
|
31
|
-
keywords = [
|
|
31
|
+
keywords = [
|
|
32
|
+
"Python Utilities", "Useful Functions", "String Utilities", "CLI Functions", "toolset", "library",
|
|
33
|
+
"System Utilities", "Useful Decorators", "IO Functions"
|
|
34
|
+
]
|
|
32
35
|
classifiers = [
|
|
33
36
|
"Development Status :: 6 - Mature",
|
|
34
37
|
"Operating System :: OS Independent",
|
|
@@ -38,84 +41,129 @@ classifiers = [
|
|
|
38
41
|
"Programming Language :: Python :: 3.11",
|
|
39
42
|
"Programming Language :: Python :: 3.12",
|
|
40
43
|
"Programming Language :: Python :: 3.13",
|
|
44
|
+
"Programming Language :: Python :: 3.14",
|
|
41
45
|
"License :: OSI Approved :: MIT License",
|
|
42
46
|
"Intended Audience :: Developers",
|
|
47
|
+
"Intended Audience :: System Administrators",
|
|
43
48
|
"Topic :: Utilities",
|
|
44
49
|
"Typing :: Typed"
|
|
45
50
|
]
|
|
46
51
|
|
|
47
|
-
# -
|
|
52
|
+
# - project runtime dependencies
|
|
48
53
|
dependencies = [
|
|
49
|
-
|
|
54
|
+
|
|
55
|
+
"xlrd~=2.0.1", # - allowed: >=2.0.1 <2.1.0
|
|
50
56
|
"xlwt==1.3.0",
|
|
51
|
-
"xlwings~=0.
|
|
57
|
+
"xlwings~=0.35",
|
|
52
58
|
"loguru==0.7.3",
|
|
53
59
|
"pyyaml==6.*",
|
|
54
60
|
"openpyxl==3.1.5",
|
|
55
|
-
"urllib3~=2.
|
|
61
|
+
"urllib3~=2.6", # - allowed: >=2.3.0 <3.0.0
|
|
56
62
|
"paramiko==4.*",
|
|
57
|
-
"requests~=2.32", # allowed: >=2.32.0 <3.0.0
|
|
58
|
-
"markdown~=3.7", # allowed: >=3.7.0 <4.0.0
|
|
59
|
-
"prettytable~=3.14", # allowed: >=3.14.0 <4.0.0
|
|
60
|
-
"fake-useragent~=2.0" # allowed: >=2.0.0 <3.0.0
|
|
63
|
+
"requests~=2.32", # - allowed: >=2.32.0 <3.0.0
|
|
64
|
+
"markdown~=3.7", # - allowed: >=3.7.0 <4.0.0
|
|
65
|
+
"prettytable~=3.14", # - allowed: >=3.14.0 <4.0.0
|
|
66
|
+
"fake-useragent~=2.0", # - allowed: >=2.0.0 <3.0.0
|
|
67
|
+
"click~=8.3"
|
|
68
|
+
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# -- additional dependencies groups (test/research/etc. - not runtime)
|
|
72
|
+
[dependency-groups]
|
|
73
|
+
|
|
74
|
+
dev = [
|
|
75
|
+
|
|
76
|
+
# -- pytest library and extensions --
|
|
77
|
+
"pytest ==9.*", # - allowed any version < 10.0.0
|
|
78
|
+
"pytest-env ==1.*",
|
|
79
|
+
"pytest-cov ==7.*",
|
|
80
|
+
"pytest-mock ==3.*",
|
|
81
|
+
"pytest-pep8 ==1.0.6",
|
|
82
|
+
"pytest-flakes ==4.0.5",
|
|
83
|
+
"pytest-isort ==4.0.0",
|
|
84
|
+
"pytest-black ==0.6.0",
|
|
85
|
+
"pytest-variables[hjson,yaml,toml] ==3.1.0",
|
|
86
|
+
|
|
87
|
+
# -- testing-related libraries --
|
|
88
|
+
"mock ==5.*",
|
|
89
|
+
"mockito ~=2",
|
|
90
|
+
"nose2 ==0.*",
|
|
91
|
+
"hypothesis ==6.*",
|
|
92
|
+
|
|
93
|
+
# -- typing libraries + typing stubs for runtime dependencies --
|
|
94
|
+
"mypy ==1.*", # - typing library
|
|
95
|
+
"types-six ==1.*",
|
|
96
|
+
"types-mock ==5.*",
|
|
97
|
+
"types-paramiko ==4.*",
|
|
98
|
+
"types-requests ==2.*",
|
|
99
|
+
"types-markdown ==3.*",
|
|
100
|
+
"types-PyYAML ==6.*",
|
|
101
|
+
"types-flake8-bugbear ==25.*",
|
|
102
|
+
|
|
103
|
+
# -- code quality control libraries
|
|
104
|
+
"flake8 ==7.*",
|
|
105
|
+
"flake8-pyproject ~=1.2",
|
|
106
|
+
"flake8-bandit ~=4.1",
|
|
107
|
+
"flake8-bugbear ==25.*",
|
|
108
|
+
"black ~=26.0", # - code formatter
|
|
109
|
+
"pylint ~=4.0",
|
|
110
|
+
"pre-commit ~=4.0", # - pre-commit hook
|
|
111
|
+
"bandit ==1.9.*",
|
|
112
|
+
"isort[requirements_deprecated_finder,pipfile_deprecated_finder] ==8.*",
|
|
113
|
+
"pyflakes ~=3.4",
|
|
114
|
+
"codeaudit ~=1.4", # - SAST local analysis
|
|
115
|
+
|
|
116
|
+
# -- profiling/debugging libraries
|
|
117
|
+
"snakeviz ==2.2.2",
|
|
118
|
+
"memory-profiler ==0.61.0",
|
|
119
|
+
"py-spy ==0.4.1",
|
|
120
|
+
"viztracer ==1.1.1",
|
|
121
|
+
|
|
122
|
+
# -- development-related process libraries
|
|
123
|
+
"sphinx ==8.* ; python_version == '3.10'", # - docs lib - for python == 3.10
|
|
124
|
+
"sphinx ==9.* ; python_version >= '3.11' and python_version < '4.0'", # - docs lib - for python > 3.10
|
|
125
|
+
|
|
126
|
+
"watchdog ==6.0.0", # - file monitor and auto-reloader
|
|
127
|
+
"responses ==0.*", # - used by many testing/code quality libraries
|
|
128
|
+
|
|
129
|
+
# -- readline module depending on the platform
|
|
130
|
+
"pyreadline3 ==3.5.4 ; sys_platform == 'win32' or sys_platform == 'cygwin'",
|
|
131
|
+
"gnureadline ==8.2.13 ; sys_platform != 'win32'"
|
|
132
|
+
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
research = [ # - research dependencies (Jupyter, etc.)
|
|
136
|
+
|
|
137
|
+
"notebook~=7.5" # allowed < 8.0.0
|
|
138
|
+
|
|
61
139
|
]
|
|
62
140
|
|
|
141
|
+
[tool.poetry]
|
|
142
|
+
packages = [{include = "pyutilities", from = "src"}]
|
|
143
|
+
|
|
144
|
+
# -- poetry :: source repositories setup
|
|
145
|
+
[[tool.poetry.source]]
|
|
146
|
+
name = "severstal_repo"
|
|
147
|
+
url = "https://repo.severstal.severstalgroup.com/artifactory/api/pypi/pypi/simple/"
|
|
148
|
+
# priority = "primary" # - primary packages source
|
|
149
|
+
priority = "supplemental" # - packages source will be used when primary is not available...
|
|
150
|
+
|
|
151
|
+
[tool.poetry.dependencies]
|
|
152
|
+
python = ">=3.10,<4"
|
|
153
|
+
|
|
63
154
|
[project.urls]
|
|
64
155
|
Homepage = "https://pypi.org/project/pyutilities"
|
|
65
156
|
Repository = "https://github.com/dmitry-ed-gusev/pyutilities"
|
|
66
157
|
Documentation = "https://github.com/dmitry-ed-gusev/pyutilities"
|
|
67
158
|
"Bug Tracker" = "https://github.com/dmitry-ed-gusev/pyutilities/issues"
|
|
68
159
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
[tool.poetry]
|
|
73
|
-
packages = [{include = "pyutilities", from = "src"}]
|
|
74
|
-
|
|
75
|
-
# -- poetry 'test' dependencies (group)
|
|
76
|
-
[tool.poetry.group.test.dependencies]
|
|
77
|
-
# -- main pytest libraries
|
|
78
|
-
pytest = "9.*" # allowed any version < 10.0.0
|
|
79
|
-
pytest-env = "1.*"
|
|
80
|
-
pytest-cov = "7.*"
|
|
81
|
-
pytest-mock = "3.*"
|
|
82
|
-
pytest-pep8 = "1.0.6"
|
|
83
|
-
pytest-flakes = "4.0.5"
|
|
84
|
-
pytest-isort = "4.0.0"
|
|
85
|
-
pytest-black = "0.6.0"
|
|
86
|
-
pytest-variables = {version = "3.1.0", extras = ["hjson", "yaml", "toml"]}
|
|
87
|
-
# -- testing-related libraries
|
|
88
|
-
mock = "5.*"
|
|
89
|
-
mockito = "1.*"
|
|
90
|
-
nose2 = "0.15.1"
|
|
91
|
-
hypothesis = "6.*"
|
|
92
|
-
# -- typing libraries
|
|
93
|
-
mypy = "1.*"
|
|
94
|
-
types-six = "1.*"
|
|
95
|
-
types-mock = "5.*"
|
|
96
|
-
types-paramiko = "4.*"
|
|
97
|
-
types-requests = "2.*"
|
|
98
|
-
types-markdown = "3.*"
|
|
99
|
-
types-PyYAML = "6.*"
|
|
100
|
-
types-flake8-bugbear = "25.*"
|
|
101
|
-
# -- code quality libraries
|
|
102
|
-
flake8 = "7.*"
|
|
103
|
-
flake8-pyproject = "1.2.3"
|
|
104
|
-
flake8-bandit = "4.1.1"
|
|
105
|
-
flake8-bugbear = "25.*"
|
|
106
|
-
black = "25.*"
|
|
107
|
-
pylint = "4.*"
|
|
108
|
-
pre-commit = "4.*"
|
|
109
|
-
bandit = "1.9.*"
|
|
110
|
-
isort = {version = "7.*", extras = ["requirements_deprecated_finder", "pipfile_deprecated_finder"]}
|
|
111
|
-
# -- other
|
|
112
|
-
responses = "0.*"
|
|
113
|
-
sphinx = "8.1.3"
|
|
114
|
-
notebook = "7.*"
|
|
160
|
+
[project.scripts]
|
|
161
|
+
pyutils = 'pyutilities.cli.pyutils:pyutils'
|
|
115
162
|
|
|
116
163
|
# -- flake8 tool
|
|
117
164
|
[tool.flake8]
|
|
118
|
-
# - list of ignored issues
|
|
165
|
+
# - list of ignored issues for the flake8 tool
|
|
166
|
+
# - (E501 - long line, F401 - unused import, E203 - whitespace before ':' - black puts it)
|
|
119
167
|
ignore = ['F401']
|
|
120
168
|
# - max cognitive complexity for statements
|
|
121
169
|
max-complexity = 10
|
|
@@ -146,7 +194,7 @@ line_length = 110
|
|
|
146
194
|
include_trailing_comma = true
|
|
147
195
|
skip_gitignore = true
|
|
148
196
|
skip_glob = ["**/migrations/*", "**/settings/*"]
|
|
149
|
-
#src_paths = ["./", "src/"]
|
|
197
|
+
# src_paths = ["./", "src/"]
|
|
150
198
|
src_paths = ["./src/"]
|
|
151
199
|
|
|
152
200
|
# -- black tool configuration
|
|
@@ -183,38 +231,36 @@ allow_untyped_defs = true
|
|
|
183
231
|
# - mypy will ignore missing types defs imports
|
|
184
232
|
ignore_missing_imports = true
|
|
185
233
|
# - force mypy to analyze as best as its can module without types defs
|
|
186
|
-
|
|
187
|
-
# plugins = ["pydantic.mypy"]
|
|
234
|
+
follow_untyped_imports = true
|
|
188
235
|
|
|
189
236
|
# [[tool.mypy.overrides]]
|
|
190
237
|
# module = "pyutilities.web.http_client"
|
|
191
238
|
# disable_error_code = ["R0913", "too-many-arguments", "unused-argument"]
|
|
192
239
|
|
|
193
|
-
# -- mypy extra configuration (for pydantic)
|
|
194
|
-
# [tool.pydantic-mypy]
|
|
195
|
-
# init_forbid_extra = true
|
|
196
|
-
# init_typed = true
|
|
197
|
-
# warn_required_dynamic_aliases = true
|
|
198
|
-
|
|
199
240
|
# -- pytest tool
|
|
200
241
|
[tool.pytest.ini_options]
|
|
201
242
|
minversion = "6.0"
|
|
202
243
|
addopts = """
|
|
203
|
-
--cov=./src/pyutilities --verbose --cov-report=html --cov-report=xml --cov-report=json --cov-report=annotate
|
|
204
|
-
--capture=sys --durations=0 --full-trace --color=auto
|
|
244
|
+
--cov=./src/pyutilities --verbose --cov-report=html --cov-report=xml --cov-report=json --cov-report=annotate --capture=sys -rx --durations=0 --full-trace --color=auto
|
|
205
245
|
"""
|
|
206
246
|
testpaths = [
|
|
207
247
|
"tests/",
|
|
208
248
|
"integration/",
|
|
209
249
|
]
|
|
250
|
+
mock_use_standalone_module = true
|
|
251
|
+
mock_traceback_monkeypatch = false
|
|
252
|
+
|
|
210
253
|
# - tests markers - tests can be grouped and executed by them
|
|
211
254
|
markers = [
|
|
255
|
+
|
|
212
256
|
"functional: these tests are significantly slower as they run the whole CLI script",
|
|
257
|
+
|
|
213
258
|
]
|
|
214
259
|
|
|
215
260
|
# -- env variables for tests in pytest
|
|
216
261
|
[tool.pytest_env]
|
|
217
|
-
MY_VARIABLE = "my_value"
|
|
262
|
+
# MY_VARIABLE = "my_value"
|
|
263
|
+
PYTHONTRACEMALLOC = 20
|
|
218
264
|
|
|
219
265
|
# -- coverage tool configuration (coveragerc)
|
|
220
266
|
[tool.coverage.run]
|
|
@@ -239,6 +285,7 @@ title = "Python Utilities :: Tests Coverage Report."
|
|
|
239
285
|
[tool.coverage.xml]
|
|
240
286
|
output = ".coverage/coverage.xml"
|
|
241
287
|
|
|
288
|
+
# -- this setting is used by coverage.py, ignored by pytest-cov
|
|
242
289
|
[tool.coverage.json]
|
|
243
290
|
output = ".coverage/coverage.json"
|
|
244
291
|
pretty_print = true
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Cmd line module for th pyutilities library - integration with CLI.
|
|
5
|
+
|
|
6
|
+
Created: Dmitrii Gusev, 17.04.2026
|
|
7
|
+
Modified: Dmitrii Gusev, 17.04.2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from pyutilities import __version__
|
|
13
|
+
|
|
14
|
+
MSG_VERSION = f"\nPy Utilities, version: {__version__}, (C) Dmitrii Gusev, 2018-2026.\n"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
@click.help_option("-h", "--help")
|
|
19
|
+
@click.version_option(__version__, "-v", "--version", message=MSG_VERSION)
|
|
20
|
+
@click.pass_context
|
|
21
|
+
def pyutils(ctx):
|
|
22
|
+
"""Module [Py Utilities], (C) Dmitrii Gusev, 2018-2026."""
|
|
23
|
+
click.echo("")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pyutils.command("version")
|
|
27
|
+
def version():
|
|
28
|
+
click.echo(__version__ + "\n")
|
|
@@ -321,7 +321,8 @@ class ConfigurationXls(Configuration):
|
|
|
321
321
|
dictionary[name] = value
|
|
322
322
|
|
|
323
323
|
elif path_to_xls.endswith("xlsx"): # load from excel file - format xlsx
|
|
324
|
-
|
|
324
|
+
# load workbook by the given path
|
|
325
|
+
excel_book = openpyxl.load_workbook(path_to_xls) # type: ignore
|
|
325
326
|
excel_sheet = excel_book[config_sheet_name] # specified sheet by name
|
|
326
327
|
self.log.debug("Loaded XLSX config. Found [%s] row(s). Loading.", excel_sheet.max_row)
|
|
327
328
|
for rownumber in range(excel_sheet.max_row):
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
IO Utilities module.
|
|
5
5
|
|
|
6
6
|
Created: Dmitrii Gusev, 04.04.2017
|
|
7
|
-
Modified: Dmitrii Gusev,
|
|
7
|
+
Modified: Dmitrii Gusev, 17.04.2026
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import errno
|
|
@@ -83,7 +83,7 @@ def str_2_file(filename: str, content: str, overwrite: bool = False, encoding: s
|
|
|
83
83
|
f.write(content)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def
|
|
86
|
+
def file_2_str(filename: str, encoding: str = DEFAULT_ENCODING) -> str:
|
|
87
87
|
"""Read content from the provided file as string/text."""
|
|
88
88
|
|
|
89
89
|
log.debug("file2str(): reading content from file: [%s].", filename)
|
|
@@ -123,8 +123,20 @@ def compress_file(input_file, output_file):
|
|
|
123
123
|
shutil.copyfileobj(f_in, f_out)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
def
|
|
127
|
-
"""Function will return the file size in
|
|
126
|
+
def file_str(file_path: str) -> int:
|
|
127
|
+
"""Function will return the file size in bytes. In case file doesn't exist/not a file/other
|
|
128
|
+
issues - the function will return -1 (negative value)."""
|
|
129
|
+
|
|
130
|
+
if os.path.isfile(file_path):
|
|
131
|
+
file_info = os.stat(file_path)
|
|
132
|
+
return file_info.st_size
|
|
133
|
+
|
|
134
|
+
return -1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def file_size_str(file_path: str) -> str:
|
|
138
|
+
"""Function will return the file size in a human-readable format with the highest size
|
|
139
|
+
mark - KB/MB/GB/TB."""
|
|
128
140
|
|
|
129
141
|
if os.path.isfile(file_path):
|
|
130
142
|
file_info = os.stat(file_path)
|
|
@@ -149,5 +149,15 @@ def dict_2_csv(dicts_list: List[Dict[str, str]], filename: str, overwrite_file:
|
|
|
149
149
|
dict_writer.writerows(dicts_list)
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def get_value_safely(dictionary: dict[str, str | None], key: str, default_value: Any) -> Any:
|
|
153
|
+
"""Safely retrieves a value from a dictionary, handling both a potential None dictionary and a
|
|
154
|
+
missing key."""
|
|
155
|
+
|
|
156
|
+
if dictionary:
|
|
157
|
+
return dictionary.get(key, default_value) # The .get() method handles the missing key gracefully
|
|
158
|
+
|
|
159
|
+
return default_value # If the dictionary itself is None, return the default value
|
|
160
|
+
|
|
161
|
+
|
|
152
162
|
if __name__ == "__main__":
|
|
153
163
|
print(MSG_MODULE_ISNT_RUNNABLE)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Useful date/time utilities and functions.
|
|
5
|
+
|
|
6
|
+
Created: Dmitrii Gusev, 22.03.2026
|
|
7
|
+
Modified: Dmitrii Gusev, 22.03.2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timedelta, timezone
|
|
11
|
+
|
|
12
|
+
from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE
|
|
13
|
+
|
|
14
|
+
MSK_TIMEZONE: timezone = timezone(timedelta(hours=3), name="Moscow Timezone (Russia)")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_timestamp(
|
|
18
|
+
current_timezone: timezone = MSK_TIMEZONE,
|
|
19
|
+
days: float = 0,
|
|
20
|
+
hours: float = 0,
|
|
21
|
+
minutes: float = 0,
|
|
22
|
+
seconds: float = 0,
|
|
23
|
+
) -> datetime:
|
|
24
|
+
"""Return timestamp: now (in the default timezone) +/- the specified time delta in hours / minutes /
|
|
25
|
+
seconds. Default timezone can be changed by the function argument."""
|
|
26
|
+
|
|
27
|
+
return datetime.now(current_timezone) + timedelta(
|
|
28
|
+
days=days, hours=hours, minutes=minutes, seconds=seconds
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
print(MSG_MODULE_ISNT_RUNNABLE)
|
|
@@ -84,5 +84,37 @@ def log_execution(func):
|
|
|
84
84
|
return wrapper
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def check_not_none(*arg_names):
|
|
88
|
+
"""
|
|
89
|
+
A decorator that checks if specified arguments are not None.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
*arg_names: Variable number of strings representing the names of
|
|
93
|
+
arguments to check for None values.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def decorator(func):
|
|
97
|
+
@functools.wraps(func)
|
|
98
|
+
def wrapper(*args, **kwargs):
|
|
99
|
+
# Get the argument names from the function signature
|
|
100
|
+
func_arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
|
|
101
|
+
|
|
102
|
+
# Map positional arguments to their names
|
|
103
|
+
arg_map = dict(zip(func_arg_names, args))
|
|
104
|
+
# Add keyword arguments
|
|
105
|
+
arg_map.update(kwargs)
|
|
106
|
+
|
|
107
|
+
for name in arg_names:
|
|
108
|
+
if name not in arg_map:
|
|
109
|
+
raise TypeError(f"Argument '{name}' not found in function signature.")
|
|
110
|
+
if arg_map[name] is None:
|
|
111
|
+
raise ValueError(f"Argument '{name}' cannot be None.")
|
|
112
|
+
return func(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
return wrapper
|
|
115
|
+
|
|
116
|
+
return decorator
|
|
117
|
+
|
|
118
|
+
|
|
87
119
|
if __name__ == "__main__":
|
|
88
120
|
print(MSG_MODULE_ISNT_RUNNABLE)
|
|
@@ -10,11 +10,11 @@ in 'fast' executing code, where logging will just add additional complexity and
|
|
|
10
10
|
Also most of the functions use LRU feature of the python language.
|
|
11
11
|
|
|
12
12
|
Created: Dmitrii Gusev, 15.04.2019
|
|
13
|
-
Modified: Dmitrii Gusev,
|
|
13
|
+
Modified: Dmitrii Gusev, 17.04.2026
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
-
from functools import
|
|
17
|
+
from functools import lru_cache
|
|
18
18
|
from re import match as re_match
|
|
19
19
|
from typing import Any, Dict, Iterable, Tuple
|
|
20
20
|
|
|
@@ -38,10 +38,13 @@ REGEX_FLOAT_2 = "^\\d+?\\.\\d+?$" # original regex with fixed warnings
|
|
|
38
38
|
REGEX_FLOAT_3 = "^[+-]?([0-9]*[.])?[0-9]+$" # simplified regex, matches: 123/123.456/.456
|
|
39
39
|
REGEX_FLOAT_4 = "^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$" # matches as previous plus: 123.
|
|
40
40
|
|
|
41
|
+
# cache setup (size) for cached functions/methods
|
|
42
|
+
LRU_CACHE_SIZE: int = 128
|
|
43
|
+
|
|
41
44
|
|
|
42
|
-
@cache
|
|
43
45
|
def trim_2_none(string: str | None, trace: bool = False) -> str | None:
|
|
44
|
-
"""Trim the provided string to None (if empty) or just strip
|
|
46
|
+
"""NOT CACHED. Trim the provided string to None (if it is None or empty - only spaces) or just strip
|
|
47
|
+
leading/trailing whitespaces and return the string."""
|
|
45
48
|
|
|
46
49
|
if string and string.strip(): # string isn't empty - trimming whitespaces
|
|
47
50
|
result = string.strip()
|
|
@@ -54,9 +57,15 @@ def trim_2_none(string: str | None, trace: bool = False) -> str | None:
|
|
|
54
57
|
return result
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
@
|
|
60
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
61
|
+
def trimc_2_none(string: str | None, trace: bool = False) -> str | None:
|
|
62
|
+
"""CACHED version of the trim_2_none()."""
|
|
63
|
+
return trim_2_none(string, trace)
|
|
64
|
+
|
|
65
|
+
|
|
58
66
|
def trim_2_empty(string: str | None, trace: bool = False) -> str:
|
|
59
|
-
"""Trim the provided string to empty string - ''
|
|
67
|
+
"""NOT CACHED. Trim the provided string to empty string - ''/"" - (if empty or None) or just
|
|
68
|
+
strip whitespaces and return the string."""
|
|
60
69
|
|
|
61
70
|
if string and string.strip(): # string isn't empty - trimming whitespaces
|
|
62
71
|
result = string.strip()
|
|
@@ -69,10 +78,16 @@ def trim_2_empty(string: str | None, trace: bool = False) -> str:
|
|
|
69
78
|
return result
|
|
70
79
|
|
|
71
80
|
|
|
72
|
-
@
|
|
73
|
-
def
|
|
74
|
-
"""
|
|
75
|
-
|
|
81
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
82
|
+
def trimc_2_empty(string: str | None, trace: bool = False) -> str:
|
|
83
|
+
"""CACHED version of the trim_2_empty()."""
|
|
84
|
+
return trim_2_empty(string, trace)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def filter_str(string: str | None, trace: bool = False) -> str | None:
|
|
88
|
+
"""NOT CACHED. Filter out all unnecessary/unwanted symbols from string (clear string) except
|
|
89
|
+
letters, numbers, spaces, commas. By default, decode input string in unicode (utf-8).
|
|
90
|
+
Accepted symbols are: letters, numbers, space, ALL_SYMBOLS (see above)."""
|
|
76
91
|
|
|
77
92
|
def accepted(char) -> bool: # internal function
|
|
78
93
|
"""Simple internal helper function."""
|
|
@@ -89,7 +104,13 @@ def filter_str(string: str | None, trace: bool = False):
|
|
|
89
104
|
return result
|
|
90
105
|
|
|
91
106
|
|
|
92
|
-
@
|
|
107
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
108
|
+
def filterc_str(string: str | None, trace: bool = False) -> str | None:
|
|
109
|
+
"""CACHED version of the filter_str()."""
|
|
110
|
+
return filter_str(string, trace)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
93
114
|
def process_url(
|
|
94
115
|
url: str, postfix: str = "", format_values: Tuple[str] | None = None, trace: bool = False
|
|
95
116
|
) -> str:
|
|
@@ -123,7 +144,7 @@ def process_url(
|
|
|
123
144
|
return result
|
|
124
145
|
|
|
125
146
|
|
|
126
|
-
@
|
|
147
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
127
148
|
def process_urls(
|
|
128
149
|
urls: Dict[str, str], postfix: str = "", format_values: Tuple[str] | None = None, trace: bool = False
|
|
129
150
|
) -> Dict[str, str]:
|
|
@@ -141,7 +162,7 @@ def process_urls(
|
|
|
141
162
|
return processed
|
|
142
163
|
|
|
143
164
|
|
|
144
|
-
@
|
|
165
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
145
166
|
def get_str_ending(string: str, symbol: str = "/", trace: bool = False) -> str:
|
|
146
167
|
"""Returns the last right part of the string after the symbol (not including the symbol itself). It is
|
|
147
168
|
most right part of the string, after the last right symbol (if there are multiple symbols).
|
|
@@ -160,7 +181,7 @@ def get_str_ending(string: str, symbol: str = "/", trace: bool = False) -> str:
|
|
|
160
181
|
return result
|
|
161
182
|
|
|
162
183
|
|
|
163
|
-
@
|
|
184
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
164
185
|
def is_number(value: str, trace: bool = False) -> bool:
|
|
165
186
|
"""Returns True if string is a number. String is checked 'as is' - no trailing/leading spaces cut, no
|
|
166
187
|
any other transformations."""
|
|
@@ -228,9 +249,8 @@ def iter_2_str(values: Iterable[Any], braces: bool = True, trace: bool = False)
|
|
|
228
249
|
return resulting_value # returning result
|
|
229
250
|
|
|
230
251
|
|
|
231
|
-
@cache
|
|
232
252
|
def coalesce(*args, trace: bool = False) -> str:
|
|
233
|
-
"""Return first not None and not empty value from provided args list."""
|
|
253
|
+
"""NOT CACHED. Return first not None and not empty value from provided args list."""
|
|
234
254
|
|
|
235
255
|
result: str = ""
|
|
236
256
|
|
|
@@ -252,7 +272,13 @@ def coalesce(*args, trace: bool = False) -> str:
|
|
|
252
272
|
return result
|
|
253
273
|
|
|
254
274
|
|
|
255
|
-
@
|
|
275
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
276
|
+
def coalescec(*args, trace: bool = False) -> str:
|
|
277
|
+
"""CACHED version of the coalesce()."""
|
|
278
|
+
return coalesce(*args, trace)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
256
282
|
def one_of_2_str(string1: str | None, string2: str | None, trace: bool = False) -> str | None:
|
|
257
283
|
"""Function returning one of two strings, if other is empty. If both are empty or filled in - method
|
|
258
284
|
returns None (empty value)."""
|
|
@@ -271,7 +297,7 @@ def one_of_2_str(string1: str | None, string2: str | None, trace: bool = False)
|
|
|
271
297
|
return result
|
|
272
298
|
|
|
273
299
|
|
|
274
|
-
@
|
|
300
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
275
301
|
def convert_bytes(num: float, trace: bool = False) -> str:
|
|
276
302
|
"""Function will convert bytes to MB.... GB... etc. for readability."""
|
|
277
303
|
|
|
@@ -288,7 +314,7 @@ def convert_bytes(num: float, trace: bool = False) -> str:
|
|
|
288
314
|
return result
|
|
289
315
|
|
|
290
316
|
|
|
291
|
-
@
|
|
317
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
292
318
|
def str_2_bool(string: str | None, trace: bool = False) -> bool:
|
|
293
319
|
"""Convert string to bool. If empty string - return False. If string is not empty - return if string,
|
|
294
320
|
converted to lower case, contains in the list: 'true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly',
|
|
@@ -316,7 +342,7 @@ def str_2_bool(string: str | None, trace: bool = False) -> bool:
|
|
|
316
342
|
return result
|
|
317
343
|
|
|
318
344
|
|
|
319
|
-
@
|
|
345
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
320
346
|
def str_2_int(string: str | None, trace: bool = False) -> int:
|
|
321
347
|
"""Convert string to integer number. Empty string or string with non-digit symbols will return 0,
|
|
322
348
|
otherwise will return int(string)."""
|
|
@@ -340,7 +366,7 @@ def str_2_int(string: str | None, trace: bool = False) -> int:
|
|
|
340
366
|
return result
|
|
341
367
|
|
|
342
368
|
|
|
343
|
-
@
|
|
369
|
+
@lru_cache(maxsize=LRU_CACHE_SIZE)
|
|
344
370
|
def str_2_float(string: str | None, trace: bool = False) -> float:
|
|
345
371
|
"""Convert string to float number. Empty string or string with non-digit symbols
|
|
346
372
|
will return 0, otherwise will return float(string)."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|