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.
Files changed (25) hide show
  1. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/PKG-INFO +17 -7
  2. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/docs/README-PUB.md +9 -2
  3. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/pyproject.toml +118 -71
  4. {pyutilities-2.1.2 → 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.2 → pyutilities-2.4.0.dev1}/src/pyutilities/config/configuration.py +2 -1
  7. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/io/io_utils.py +16 -4
  8. {pyutilities-2.1.2 → 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.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/decorators.py +32 -0
  11. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/string_utils.py +47 -21
  12. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/LICENSE +0 -0
  13. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/__init__.py +0 -0
  14. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/cli/fedit.py +0 -0
  15. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/config/__init__.py +0 -0
  16. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/defaults.py +0 -0
  17. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/exception.py +0 -0
  18. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/io/__init__.py +0 -0
  19. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/logging.py +0 -0
  20. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/__init__.py +0 -0
  21. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/sys/sys_utils.py +0 -0
  22. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/__init__.py +0 -0
  23. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/utils/lambdas.py +0 -0
  24. {pyutilities-2.1.2 → pyutilities-2.4.0.dev1}/src/pyutilities/web/__init__.py +0 -0
  25. {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.1.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.3,<3.0)
34
+ Requires-Dist: urllib3 (>=2.6,<3.0)
32
35
  Requires-Dist: xlrd (>=2.0.1,<2.1.0)
33
- Requires-Dist: xlwings (>=0.33.9,<0.34.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.1.2, 2018 - 2025, Dmitrii Gusev
44
- *Last update: 28.11.2025*
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.1.2, 2018 - 2025, Dmitrii Gusev
4
- *Last update: 28.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,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, 28.11.2025
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.1.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 = ["Python Utilities", "Useful Functions", "String Utilities", "CLI Functions", "toolset", "library", "System Utilities"]
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
- # - main project dependencies
52
+ # - project runtime dependencies
48
53
  dependencies = [
49
- "xlrd~=2.0.1", # allowed: >=2.0.1 <2.1.0
54
+
55
+ "xlrd~=2.0.1", # - allowed: >=2.0.1 <2.1.0
50
56
  "xlwt==1.3.0",
51
- "xlwings~=0.33.9", # allowed: >=0.33.9 <0.34.0
57
+ "xlwings~=0.35",
52
58
  "loguru==0.7.3",
53
59
  "pyyaml==6.*",
54
60
  "openpyxl==3.1.5",
55
- "urllib3~=2.3", # allowed: >=2.3.0 <3.0.0
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
- # [project.scripts]
70
- # my_package_cli = 'my_package.console:run'
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 (E501 - too long line, F401 - imported but unused, E203 - whitespace before ':' - black formatter puts it - ???)
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
- # follow_untyped_imports = true
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
@@ -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)."""
File without changes