pyutilities 2.1.1__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.
Files changed (26) hide show
  1. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/PKG-INFO +22 -9
  2. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/docs/README-PUB.md +11 -4
  3. pyutilities-2.4.0.dev1/pyproject.toml +291 -0
  4. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/__init__.py +2 -0
  5. pyutilities-2.4.0.dev1/src/pyutilities/cli/pyutils.py +28 -0
  6. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/config/configuration.py +2 -1
  7. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/io/io_utils.py +16 -4
  8. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/common_utils.py +10 -0
  9. pyutilities-2.4.0.dev1/src/pyutilities/utils/datetime_utils.py +33 -0
  10. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/decorators.py +32 -0
  11. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/string_utils.py +47 -21
  12. pyutilities-2.1.1/pyproject.toml +0 -241
  13. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/LICENSE +0 -0
  14. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/__init__.py +0 -0
  15. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/fedit.py +0 -0
  16. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/config/__init__.py +0 -0
  17. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/defaults.py +0 -0
  18. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/exception.py +0 -0
  19. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/io/__init__.py +0 -0
  20. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/logging.py +0 -0
  21. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/__init__.py +0 -0
  22. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/sys_utils.py +0 -0
  23. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/__init__.py +0 -0
  24. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/lambdas.py +0 -0
  25. {pyutilities-2.1.1 → pyutilities-2.4.0.dev1}/src/pyutilities/web/__init__.py +0 -0
  26. {pyutilities-2.1.1 → 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.1.1
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
@@ -13,10 +13,16 @@ Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
16
20
  Classifier: License :: OSI Approved :: MIT License
17
21
  Classifier: Intended Audience :: Developers
22
+ Classifier: Intended Audience :: System Administrators
18
23
  Classifier: Topic :: Utilities
19
24
  Classifier: Typing :: Typed
25
+ Requires-Dist: click (>=8.3,<9.0)
20
26
  Requires-Dist: fake-useragent (>=2.0,<3.0)
21
27
  Requires-Dist: loguru (==0.7.3)
22
28
  Requires-Dist: markdown (>=3.7,<4.0)
@@ -25,9 +31,9 @@ Requires-Dist: paramiko (==4.*)
25
31
  Requires-Dist: prettytable (>=3.14,<4.0)
26
32
  Requires-Dist: pyyaml (==6.*)
27
33
  Requires-Dist: requests (>=2.32,<3.0)
28
- Requires-Dist: urllib3 (>=2.3,<3.0)
34
+ Requires-Dist: urllib3 (>=2.6,<3.0)
29
35
  Requires-Dist: xlrd (>=2.0.1,<2.1.0)
30
- Requires-Dist: xlwings (>=0.33.9,<0.34.0)
36
+ Requires-Dist: xlwings (>=0.35,<1.0)
31
37
  Requires-Dist: xlwt (==1.3.0)
32
38
  Project-URL: Bug Tracker, https://github.com/dmitry-ed-gusev/pyutilities/issues
33
39
  Project-URL: Documentation, https://github.com/dmitry-ed-gusev/pyutilities
@@ -37,8 +43,8 @@ Description-Content-Type: text/markdown
37
43
 
38
44
  # Python Utilities Library
39
45
 
40
- Version 2.1.1, 2018 - 2025, Dmitrii Gusev
41
- *Last update: 25.11.2025*
46
+ Version 2.2.0, 2018 - 2026, Dmitrii Gusev
47
+ *Last update: XX.XX.2026*
42
48
 
43
49
  ## Project Description
44
50
 
@@ -46,11 +52,18 @@ Useful utilities functions for various needs for the python 3.10+. Containing 'g
46
52
 
47
53
  ## Versions history
48
54
 
49
- ### 2.1.1
55
+ ### 2.2.0
56
+
57
+ Added new functions:
58
+
59
+ - func1()
60
+ - func2()
61
+
62
+ ### 2.1.2
50
63
 
51
64
  Major release with complete set of useful 'general purpose' functions. Documentation 'is coming'...
52
65
 
53
- **Warning! Version 2.1.0 is intended only for the python version = 3.10 and may be installed only for it!**
66
+ **Warning! Versions 2.1.0, 2.1.1 are intended only for the python version = 3.10 and may be installed only for it!**
54
67
 
55
68
  ### Versions before 2.x.x
56
69
 
@@ -1,7 +1,7 @@
1
1
  # Python Utilities Library
2
2
 
3
- Version 2.1.1, 2018 - 2025, Dmitrii Gusev
4
- *Last update: 25.11.2025*
3
+ Version 2.2.0, 2018 - 2026, Dmitrii Gusev
4
+ *Last update: XX.XX.2026*
5
5
 
6
6
  ## Project Description
7
7
 
@@ -9,11 +9,18 @@ Useful utilities functions for various needs for the python 3.10+. Containing 'g
9
9
 
10
10
  ## Versions history
11
11
 
12
- ### 2.1.1
12
+ ### 2.2.0
13
+
14
+ Added new functions:
15
+
16
+ - func1()
17
+ - func2()
18
+
19
+ ### 2.1.2
13
20
 
14
21
  Major release with complete set of useful 'general purpose' functions. Documentation 'is coming'...
15
22
 
16
- **Warning! Version 2.1.0 is intended only for the python version = 3.10 and may be installed only for it!**
23
+ **Warning! Versions 2.1.0, 2.1.1 are intended only for the python version = 3.10 and may be installed only for it!**
17
24
 
18
25
  ### Versions before 2.x.x
19
26
 
@@ -0,0 +1,291 @@
1
+ ####################################################################################################
2
+ #
3
+ # Main configuration file for the [pyutilities] library. The whole project configuration (any
4
+ # setting) should be put here. Also configuration for dependencies and all code quality tools.
5
+ #
6
+ # Notes (code quality, etc.):
7
+ # - project line length = 110 symbols (black, flake8)
8
+ # - most of modules should be typed (used mypy stubs libraries)
9
+ #
10
+ # Created: Dmitrii Gusev, 30.05.2025
11
+ # Modified: Dmitrii Gusev, 19.04.2026
12
+ #
13
+ ####################################################################################################
14
+
15
+ # -- build system information
16
+ [build-system]
17
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
18
+ build-backend = "poetry.core.masonry.api"
19
+
20
+ # -- project / poetry utility meta-information
21
+ [project]
22
+ name = "pyutilities"
23
+ version = "2.4.0dev1"
24
+ description = "PyUtilities :: Useful python 3+ utilities library for various use cases."
25
+ authors = [
26
+ {name = "Dmitrii Gusev", email = "dmitry.ed.gusev@gmail.com"},
27
+ ]
28
+ license = "MIT"
29
+ readme = "docs/README-PUB.md"
30
+ requires-python = ">=3.10,<4"
31
+ keywords = [
32
+ "Python Utilities", "Useful Functions", "String Utilities", "CLI Functions", "toolset", "library",
33
+ "System Utilities", "Useful Decorators", "IO Functions"
34
+ ]
35
+ classifiers = [
36
+ "Development Status :: 6 - Mature",
37
+ "Operating System :: OS Independent",
38
+ "Programming Language :: Python",
39
+ "Programming Language :: Python :: 3",
40
+ "Programming Language :: Python :: 3.10",
41
+ "Programming Language :: Python :: 3.11",
42
+ "Programming Language :: Python :: 3.12",
43
+ "Programming Language :: Python :: 3.13",
44
+ "Programming Language :: Python :: 3.14",
45
+ "License :: OSI Approved :: MIT License",
46
+ "Intended Audience :: Developers",
47
+ "Intended Audience :: System Administrators",
48
+ "Topic :: Utilities",
49
+ "Typing :: Typed"
50
+ ]
51
+
52
+ # - project runtime dependencies
53
+ dependencies = [
54
+
55
+ "xlrd~=2.0.1", # - allowed: >=2.0.1 <2.1.0
56
+ "xlwt==1.3.0",
57
+ "xlwings~=0.35",
58
+ "loguru==0.7.3",
59
+ "pyyaml==6.*",
60
+ "openpyxl==3.1.5",
61
+ "urllib3~=2.6", # - allowed: >=2.3.0 <3.0.0
62
+ "paramiko==4.*",
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
+
139
+ ]
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
+
154
+ [project.urls]
155
+ Homepage = "https://pypi.org/project/pyutilities"
156
+ Repository = "https://github.com/dmitry-ed-gusev/pyutilities"
157
+ Documentation = "https://github.com/dmitry-ed-gusev/pyutilities"
158
+ "Bug Tracker" = "https://github.com/dmitry-ed-gusev/pyutilities/issues"
159
+
160
+ [project.scripts]
161
+ pyutils = 'pyutilities.cli.pyutils:pyutils'
162
+
163
+ # -- flake8 tool
164
+ [tool.flake8]
165
+ # - list of ignored issues for the flake8 tool
166
+ # - (E501 - long line, F401 - unused import, E203 - whitespace before ':' - black puts it)
167
+ ignore = ['F401']
168
+ # - max cognitive complexity for statements
169
+ max-complexity = 10
170
+ # - line length aligned with same setting for [black] (88 chars by default) -> project value = 110
171
+ max-line-length = 110
172
+ # - ignored violations, specific per files
173
+ per-file-ignores = [
174
+ 'tests/**:S101, S311',
175
+ ]
176
+
177
+ # -- pylint tool
178
+ [tool.pylint.MASTER]
179
+ disable = [
180
+ 'C0114', # missing-module-docstring
181
+ 'C0115', # missing-class-docstring
182
+ 'C0116', # missing-function-docstring
183
+ ]
184
+
185
+ [tool.pylint.FORMAT]
186
+ max-line-length=110
187
+ max-module-lines=1000
188
+
189
+ # -- isort tool
190
+ [tool.isort]
191
+ profile = 'black'
192
+ multi_line_output = 3
193
+ line_length = 110
194
+ include_trailing_comma = true
195
+ skip_gitignore = true
196
+ skip_glob = ["**/migrations/*", "**/settings/*"]
197
+ # src_paths = ["./", "src/"]
198
+ src_paths = ["./src/"]
199
+
200
+ # -- black tool configuration
201
+ [tool.black]
202
+ line_length = 110
203
+ target-version = ['py310']
204
+ extend-exclude = '''
205
+ (
206
+ migrations # Alembic DB migrations
207
+ )
208
+ '''
209
+
210
+ # -- bandit tool
211
+ [tool.bandit]
212
+ exclude_dirs = ["tests"]
213
+ tests = ["B201", "B301"]
214
+ skips = ["B101", "B601"]
215
+
216
+ # -- mypy tool
217
+ [tool.mypy]
218
+ mypy_path = "./"
219
+ python_version = "3.10"
220
+ follow_imports = "silent"
221
+ strict_optional = true
222
+ warn_redundant_casts = true
223
+ warn_unused_ignores = true
224
+ warn_return_any = true
225
+ warn_unused_configs = true
226
+ disallow_any_generics = true
227
+ check_untyped_defs = true
228
+ no_implicit_reexport = true
229
+ allow_untyped_defs = true
230
+ # disallow_untyped_defs = true
231
+ # - mypy will ignore missing types defs imports
232
+ ignore_missing_imports = true
233
+ # - force mypy to analyze as best as its can module without types defs
234
+ follow_untyped_imports = true
235
+
236
+ # [[tool.mypy.overrides]]
237
+ # module = "pyutilities.web.http_client"
238
+ # disable_error_code = ["R0913", "too-many-arguments", "unused-argument"]
239
+
240
+ # -- pytest tool
241
+ [tool.pytest.ini_options]
242
+ minversion = "6.0"
243
+ addopts = """
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
245
+ """
246
+ testpaths = [
247
+ "tests/",
248
+ "integration/",
249
+ ]
250
+ mock_use_standalone_module = true
251
+ mock_traceback_monkeypatch = false
252
+
253
+ # - tests markers - tests can be grouped and executed by them
254
+ markers = [
255
+
256
+ "functional: these tests are significantly slower as they run the whole CLI script",
257
+
258
+ ]
259
+
260
+ # -- env variables for tests in pytest
261
+ [tool.pytest_env]
262
+ # MY_VARIABLE = "my_value"
263
+ PYTHONTRACEMALLOC = 20
264
+
265
+ # -- coverage tool configuration (coveragerc)
266
+ [tool.coverage.run]
267
+ branch = true
268
+ data_file = ".coverage/.coverage"
269
+ omit = [
270
+ '*__init__*',
271
+ '*temp*',
272
+ '*tests\*'
273
+ ]
274
+
275
+ [tool.coverage.report]
276
+ show_missing = true
277
+ # - show 2 decimal digits for coverage report values
278
+ precision = 2
279
+ # skip_covered = true
280
+
281
+ [tool.coverage.html]
282
+ directory = ".coverage/htmlcoverage"
283
+ title = "Python Utilities :: Tests Coverage Report."
284
+
285
+ [tool.coverage.xml]
286
+ output = ".coverage/coverage.xml"
287
+
288
+ # -- this setting is used by coverage.py, ignored by pytest-cov
289
+ [tool.coverage.json]
290
+ output = ".coverage/coverage.json"
291
+ pretty_print = true
@@ -1,3 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  # This code should be put in <your_package_name>/__init__.py or <your_package_name>/_version.py
2
4
  # The library version now is programmatically accessible (from code).
3
5
 
@@ -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
- excel_book = openpyxl.load_workbook(path_to_xls) # workbook
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, 25.11.2025
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 file2str(filename: str, encoding: str = DEFAULT_ENCODING) -> str:
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 file_size(file_path: str) -> str:
127
- """Function will return the file size in readable format with the highest size mark - KB/MB/GB/TB."""
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, 23.11.2025
13
+ Modified: Dmitrii Gusev, 17.04.2026
14
14
  """
15
15
 
16
16
  import logging
17
- from functools import cache
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 leading/trailing whitespaces."""
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
- @cache
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 - '' or "" - (if empty) or just strip whitespaces."""
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
- @cache
73
- def filter_str(string: str | None, trace: bool = False):
74
- """Filter out all unnecessary/unwanted symbols from string (clear string) except letters, numbers,
75
- spaces, commas. By default, decode input string in unicode (utf-8)."""
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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
- @cache
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)."""
@@ -1,241 +0,0 @@
1
- ####################################################################################################
2
- #
3
- # Main configuration file for the [pyutilities] library. The whole project configuration (any
4
- # setting) should be put here. Also configuration for dependencies and all code quality tools.
5
- #
6
- # Notes (code quality, etc.):
7
- # - project line length = 110 symbols (black, flake8)
8
- # - most of modules should be typed (used mypy stubs libraries)
9
- #
10
- # Created: Dmitrii Gusev, 30.05.2025
11
- # Modified: Dmitrii Gusev, 28.11.2025
12
- #
13
- ####################################################################################################
14
-
15
- # -- build system information
16
- [build-system]
17
- requires = ["poetry-core>=2.0.0,<3.0.0"]
18
- build-backend = "poetry.core.masonry.api"
19
-
20
- # -- project / poetry utility meta-information
21
- [project]
22
- name = "pyutilities"
23
- version = "2.1.1"
24
- description = "PyUtilities :: Useful python 3 utilities library for various use cases."
25
- authors = [
26
- {name = "Dmitrii Gusev", email = "dmitry.ed.gusev@gmail.com"},
27
- ]
28
- license = "MIT"
29
- readme = "docs/README-PUB.md"
30
- requires-python = ">=3.10,<4"
31
- keywords = ["Python Utilities", "Useful Functions", "String Utilities", "CLI Functions", "toolset", "library", "System Utilities"]
32
- classifiers = [
33
- "Development Status :: 6 - Mature",
34
- "Operating System :: OS Independent",
35
- "Programming Language :: Python",
36
- "Programming Language :: Python :: 3",
37
- "Programming Language :: Python :: 3.10",
38
- "License :: OSI Approved :: MIT License",
39
- "Intended Audience :: Developers",
40
- "Topic :: Utilities",
41
- "Typing :: Typed"
42
- ]
43
-
44
- # - main project dependencies
45
- dependencies = [
46
- "xlrd~=2.0.1", # allowed: >=2.0.1 <2.1.0
47
- "xlwt==1.3.0",
48
- "xlwings~=0.33.9", # allowed: >=0.33.9 <0.34.0
49
- "loguru==0.7.3",
50
- "pyyaml==6.*",
51
- "openpyxl==3.1.5",
52
- "urllib3~=2.3", # allowed: >=2.3.0 <3.0.0
53
- "paramiko==4.*",
54
- "requests~=2.32", # allowed: >=2.32.0 <3.0.0
55
- "markdown~=3.7", # allowed: >=3.7.0 <4.0.0
56
- "prettytable~=3.14", # allowed: >=3.14.0 <4.0.0
57
- "fake-useragent~=2.0" # allowed: >=2.0.0 <3.0.0
58
- ]
59
-
60
- [project.urls]
61
- homepage = "https://pypi.org/project/pyutilities"
62
- repository = "https://github.com/dmitry-ed-gusev/pyutilities"
63
- documentation = "https://github.com/dmitry-ed-gusev/pyutilities"
64
- "Bug Tracker" = "https://github.com/dmitry-ed-gusev/pyutilities/issues"
65
-
66
- # [project.scripts]
67
- # my_package_cli = 'my_package.console:run'
68
-
69
- [tool.poetry]
70
- packages = [{include = "pyutilities", from = "src"}]
71
-
72
- # -- poetry 'test' dependencies (group)
73
- [tool.poetry.group.test.dependencies]
74
- # -- main pytest libraries
75
- pytest = "9.*" # allowed any version < 10.0.0
76
- pytest-env = "1.*"
77
- pytest-cov = "7.*"
78
- pytest-mock = "3.*"
79
- pytest-pep8 = "1.0.6"
80
- pytest-flakes = "4.0.5"
81
- pytest-isort = "4.0.0"
82
- pytest-black = "0.6.0"
83
- pytest-variables = {version = "3.1.0", extras = ["hjson", "yaml", "toml"]}
84
- # -- testing-related libraries
85
- mock = "5.*"
86
- mockito = "1.*"
87
- nose2 = "0.15.1"
88
- hypothesis = "6.*"
89
- # -- typing libraries
90
- mypy = "1.*"
91
- types-six = "1.*"
92
- types-mock = "5.*"
93
- types-paramiko = "4.*"
94
- types-requests = "2.*"
95
- types-markdown = "3.*"
96
- types-PyYAML = "6.*"
97
- types-flake8-bugbear = "25.*"
98
- # -- code quality libraries
99
- flake8 = "7.*"
100
- flake8-pyproject = "1.2.3"
101
- flake8-bandit = "4.1.1"
102
- flake8-bugbear = "25.*"
103
- black = "25.*"
104
- pylint = "4.*"
105
- pre-commit = "4.*"
106
- bandit = "1.9.*"
107
- isort = {version = "7.*", extras = ["requirements_deprecated_finder", "pipfile_deprecated_finder"]}
108
- # -- other
109
- responses = "0.*"
110
- sphinx = "8.1.3"
111
- notebook = "7.*"
112
-
113
- # -- flake8 tool
114
- [tool.flake8]
115
- # - list of ignored issues (E501 - too long line, F401 - imported but unused, E203 - whitespace before ':' - black formatter puts it - ???)
116
- ignore = ['F401']
117
- # - max cognitive complexity for statements
118
- max-complexity = 10
119
- # - line length aligned with same setting for [black] (88 chars by default) -> project value = 110
120
- max-line-length = 110
121
- # - ignored violations, specific per files
122
- per-file-ignores = [
123
- 'tests/**:S101, S311',
124
- ]
125
-
126
- # -- pylint tool
127
- [tool.pylint.MASTER]
128
- disable = [
129
- 'C0114', # missing-module-docstring
130
- 'C0115', # missing-class-docstring
131
- 'C0116', # missing-function-docstring
132
- ]
133
-
134
- [tool.pylint.FORMAT]
135
- max-line-length=110
136
- max-module-lines=1000
137
-
138
- # -- isort tool
139
- [tool.isort]
140
- profile = 'black'
141
- multi_line_output = 3
142
- line_length = 110
143
- include_trailing_comma = true
144
- skip_gitignore = true
145
- skip_glob = ["**/migrations/*", "**/settings/*"]
146
- #src_paths = ["./", "src/"]
147
- src_paths = ["./src/"]
148
-
149
- # -- black tool configuration
150
- [tool.black]
151
- line_length = 110
152
- target-version = ['py310']
153
- extend-exclude = '''
154
- (
155
- migrations # Alembic DB migrations
156
- )
157
- '''
158
-
159
- # -- bandit tool
160
- [tool.bandit]
161
- exclude_dirs = ["tests"]
162
- tests = ["B201", "B301"]
163
- skips = ["B101", "B601"]
164
-
165
- # -- mypy tool
166
- [tool.mypy]
167
- mypy_path = "./"
168
- python_version = "3.10"
169
- follow_imports = "silent"
170
- strict_optional = true
171
- warn_redundant_casts = true
172
- warn_unused_ignores = true
173
- warn_return_any = true
174
- warn_unused_configs = true
175
- disallow_any_generics = true
176
- check_untyped_defs = true
177
- no_implicit_reexport = true
178
- allow_untyped_defs = true
179
- # disallow_untyped_defs = true
180
- # - mypy will ignore missing types defs imports
181
- ignore_missing_imports = true
182
- # - force mypy to analyze as best as its can module without types defs
183
- # follow_untyped_imports = true
184
- # plugins = ["pydantic.mypy"]
185
-
186
- # [[tool.mypy.overrides]]
187
- # module = "pyutilities.web.http_client"
188
- # disable_error_code = ["R0913", "too-many-arguments", "unused-argument"]
189
-
190
- # -- mypy extra configuration (for pydantic)
191
- # [tool.pydantic-mypy]
192
- # init_forbid_extra = true
193
- # init_typed = true
194
- # warn_required_dynamic_aliases = true
195
-
196
- # -- pytest tool
197
- [tool.pytest.ini_options]
198
- minversion = "6.0"
199
- addopts = """
200
- --cov=./src/pyutilities --verbose --cov-report=html --cov-report=xml --cov-report=json --cov-report=annotate
201
- --capture=sys --durations=0 --full-trace --color=auto
202
- """
203
- testpaths = [
204
- "tests/",
205
- "integration/",
206
- ]
207
- # - tests markers - tests can be grouped and executed by them
208
- markers = [
209
- "functional: these tests are significantly slower as they run the whole CLI script",
210
- ]
211
-
212
- # -- env variables for tests in pytest
213
- [tool.pytest_env]
214
- MY_VARIABLE = "my_value"
215
-
216
- # -- coverage tool configuration (coveragerc)
217
- [tool.coverage.run]
218
- branch = true
219
- data_file = ".coverage/.coverage"
220
- omit = [
221
- '*__init__*',
222
- '*temp*',
223
- '*tests\*'
224
- ]
225
-
226
- [tool.coverage.report]
227
- show_missing = true
228
- # - show 2 decimal digits for coverage report values
229
- precision = 2
230
- # skip_covered = true
231
-
232
- [tool.coverage.html]
233
- directory = ".coverage/htmlcoverage"
234
- title = "Python Utilities :: Tests Coverage Report."
235
-
236
- [tool.coverage.xml]
237
- output = ".coverage/coverage.xml"
238
-
239
- [tool.coverage.json]
240
- output = ".coverage/coverage.json"
241
- pretty_print = true
File without changes