nano-dev-utils 0.3.4__tar.gz → 0.4.1__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 (21) hide show
  1. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/workspace.xml +45 -15
  2. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/PKG-INFO +35 -26
  3. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/README.md +35 -26
  4. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/pyproject.toml +1 -1
  5. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/src/nano_dev_utils/release_ports.py +2 -7
  6. nano_dev_utils-0.4.1/src/nano_dev_utils/timers.py +39 -0
  7. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/tests/testing_release_ports.py +4 -3
  8. nano_dev_utils-0.3.4/src/nano_dev_utils/timers.py +0 -35
  9. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.gitignore +0 -0
  10. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/.gitignore +0 -0
  11. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  12. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/misc.xml +0 -0
  13. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/modules.xml +0 -0
  14. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/nano_dev_utils.iml +0 -0
  15. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/.idea/vcs.xml +0 -0
  16. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/LICENSE.md +0 -0
  17. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/src/nano_dev_utils/__init__.py +0 -0
  18. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/src/nano_dev_utils/dynamic_importer.py +0 -0
  19. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/tests/__init__.py +0 -0
  20. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/tests/testing_dynamic_importer.py +0 -0
  21. {nano_dev_utils-0.3.4 → nano_dev_utils-0.4.1}/tests/testing_timer.py +0 -0
@@ -4,7 +4,11 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="96bbbefe-efb6-42c4-93da-e069ac3e654f" name="Changes" comment="minor update" />
7
+ <list default="true" id="96bbbefe-efb6-42c4-93da-e069ac3e654f" name="Changes" comment="release_ports: Externalizing logger's config to a user's app. &#10;timers: improve code, docstring and type hinting&#10;README updates IAW with the above changes &#10;Version update: 0.4.0">
8
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/tests/testing_release_ports.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/testing_release_ports.py" afterDir="false" />
11
+ </list>
8
12
  <option name="SHOW_DIALOG" value="false" />
9
13
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
14
  <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -27,18 +31,18 @@
27
31
  <option name="showLibraryContents" value="true" />
28
32
  <option name="showMembers" value="true" />
29
33
  </component>
30
- <component name="PropertiesComponent"><![CDATA[{
31
- "keyToString": {
32
- "Python tests.Python tests in testing_release_ports.py.executor": "Run",
33
- "Python tests.Python tests in testing_timer.py.executor": "Run",
34
- "Python tests.Python tests in tests.executor": "Run",
35
- "RunOnceActivity.ShowReadmeOnStart": "true",
36
- "git-widget-placeholder": "master",
37
- "ignore.virus.scanning.warn.message": "true",
38
- "last_opened_file_path": "C:/GitHubWS/nano_dev_utils",
39
- "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
34
+ <component name="PropertiesComponent">{
35
+ &quot;keyToString&quot;: {
36
+ &quot;Python tests.Python tests in testing_release_ports.py.executor&quot;: &quot;Run&quot;,
37
+ &quot;Python tests.Python tests in testing_timer.py.executor&quot;: &quot;Run&quot;,
38
+ &quot;Python tests.Python tests in tests.executor&quot;: &quot;Run&quot;,
39
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
40
+ &quot;git-widget-placeholder&quot;: &quot;master&quot;,
41
+ &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
42
+ &quot;last_opened_file_path&quot;: &quot;C:/GitHubWS/nano_dev_utils&quot;,
43
+ &quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;
40
44
  }
41
- }]]></component>
45
+ }</component>
42
46
  <component name="RunManager" selected="Python tests.Python tests in tests">
43
47
  <configuration name="Python tests in testing_release_ports.py" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
44
48
  <module name="nano_dev_utils" />
@@ -88,8 +92,8 @@
88
92
  <recent_temporary>
89
93
  <list>
90
94
  <item itemvalue="Python tests.Python tests in tests" />
91
- <item itemvalue="Python tests.Python tests in testing_timer.py" />
92
95
  <item itemvalue="Python tests.Python tests in testing_release_ports.py" />
96
+ <item itemvalue="Python tests.Python tests in testing_timer.py" />
93
97
  <item itemvalue="Python tests.Python tests in testing_release_ports.py" />
94
98
  </list>
95
99
  </recent_temporary>
@@ -238,7 +242,31 @@
238
242
  <option name="project" value="LOCAL" />
239
243
  <updated>1745715934159</updated>
240
244
  </task>
241
- <option name="localTasksCounter" value="17" />
245
+ <task id="LOCAL-00017" summary="readme typo fixes">
246
+ <option name="closed" value="true" />
247
+ <created>1745716694896</created>
248
+ <option name="number" value="00017" />
249
+ <option name="presentableId" value="LOCAL-00017" />
250
+ <option name="project" value="LOCAL" />
251
+ <updated>1745716694896</updated>
252
+ </task>
253
+ <task id="LOCAL-00018" summary="minor update">
254
+ <option name="closed" value="true" />
255
+ <created>1745716727869</created>
256
+ <option name="number" value="00018" />
257
+ <option name="presentableId" value="LOCAL-00018" />
258
+ <option name="project" value="LOCAL" />
259
+ <updated>1745716727869</updated>
260
+ </task>
261
+ <task id="LOCAL-00019" summary="release_ports: Externalizing logger's config to a user's app. &#10;timers: improve code, docstring and type hinting&#10;README updates IAW with the above changes &#10;Version update: 0.4.0">
262
+ <option name="closed" value="true" />
263
+ <created>1745763645981</created>
264
+ <option name="number" value="00019" />
265
+ <option name="presentableId" value="LOCAL-00019" />
266
+ <option name="project" value="LOCAL" />
267
+ <updated>1745763645981</updated>
268
+ </task>
269
+ <option name="localTasksCounter" value="20" />
242
270
  <servers />
243
271
  </component>
244
272
  <component name="Vcs.Log.Tabs.Properties">
@@ -265,7 +293,9 @@
265
293
  <MESSAGE value="dunder init update" />
266
294
  <MESSAGE value="fixed authors and url for pip show" />
267
295
  <MESSAGE value="version update" />
296
+ <MESSAGE value="readme typo fixes" />
268
297
  <MESSAGE value="minor update" />
269
- <option name="LAST_COMMIT_MESSAGE" value="minor update" />
298
+ <MESSAGE value="release_ports: Externalizing logger's config to a user's app. &#10;timers: improve code, docstring and type hinting&#10;README updates IAW with the above changes &#10;Version update: 0.4.0" />
299
+ <option name="LAST_COMMIT_MESSAGE" value="release_ports: Externalizing logger's config to a user's app. &#10;timers: improve code, docstring and type hinting&#10;README updates IAW with the above changes &#10;Version update: 0.4.0" />
270
300
  </component>
271
301
  </project>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nano_dev_utils
3
- Version: 0.3.4
3
+ Version: 0.4.1
4
4
  Summary: A collection of small Python utilities for developers.
5
5
  Project-URL: Homepage, https://github.com/yaronday/nano_utils
6
6
  Project-URL: Issues, https://github.com/yaronday/nano_utils/issues
@@ -24,16 +24,17 @@ This module provides a `Timer` class for measuring the execution time of code bl
24
24
 
25
25
  #### `Timer` Class
26
26
 
27
- * **`__init__(self, precision=4, verbose=False)`**: Initializes a `Timer` instance.
28
- * `precision` (int, optional): The number of decimal places to record and
29
- * display time durations. Defaults to 4.
30
- * `verbose` (bool, optional): If `True`, the function's arguments and keyword
31
- * arguments will be included in the printed timing output. Defaults to `False`.
27
+ * **`__init__(self, precision: int = 4, verbose: bool = False)`**: Initializes a `Timer` instance.
28
+ * `precision`: The number of decimal places to record and
29
+ display time durations. Defaults to 4.
30
+ * `verbose`: Optionally displays the function's positional arguments (args) and keyword arguments (kwargs).
31
+ Defaults to `False`.
32
32
 
33
- * **`timeit(self, func)`**: A decorator that measures the execution time of the decorated function.
34
- * When the decorated function is called, this decorator records the start and end times,
35
- * calculates the total execution time, prints the function name and execution time
36
- * (optionally including arguments if `verbose` is `True`), and returns the result of the original function.
33
+ * **`timeit(self, func: Callable[P, R]) -> Callable[P, R]`**:
34
+ Decorator that times function execution with automatic unit scaling.
35
+ * When the decorated function is called, this decorator records the start and end times,
36
+ calculates the total execution time, prints the function name and execution
37
+ time (optionally including arguments), and returns the result of the original function.
37
38
 
38
39
  #### Example Usage:
39
40
 
@@ -66,7 +67,7 @@ This module provides an `Importer` class for lazy loading and caching module imp
66
67
  * **`import_mod_from_lib(self, library: str, module_name: str) -> ModuleType | Any`**: Lazily imports a module from a specified library and caches it.
67
68
  * `library` (str): The name of the library (e.g., "os", "requests").
68
69
  * `module_name` (str): The name of the module to import within the library (e.g., "path", "get").
69
- * Returns the imported module. If the module has already been imported, it returns the cached instance.
70
+ * Returns the imported module. If the module has already been imported, it returns a cached instance.
70
71
  * Raises `ImportError` if the module cannot be found.
71
72
 
72
73
  #### Example Usage:
@@ -90,34 +91,42 @@ print(f"Imported os.path again (cached): {os_path_again}")
90
91
  ### `release_ports.py`
91
92
 
92
93
  This module provides a `PortsRelease` class to identify and release processes
93
- listening on specified TCP ports. It supports Windows, Linux, and macOS.
94
+ listening on specified TCP ports.
95
+ It supports Windows, Linux, and macOS.
94
96
 
95
97
  #### `PortsRelease` Class
96
98
 
97
- * **`__init__(self, default_ports: Optional[list[int]] = None)`**:
99
+ * **`__init__(self, default_ports: list[int] | None = None)`**:
98
100
  * Initializes a `PortsRelease` instance.
99
- * `default_ports` (`list[int]`, *optional*): A list of default ports to manage.
100
- * If not provided, it defaults to `[6277, 6274]`.
101
+ * `default_ports`: A list of default ports to manage. If not provided, it defaults to `[6277, 6274]`.
101
102
 
102
- * **`get_pid_by_port(port: int) -> Optional[int]`**: A static method that attempts to
103
- * find the process ID (PID) listening on the given `port`. It uses platform-specific
104
- * commands (`netstat`, `ss`, `lsof`). Returns the PID if found, otherwise `None`.
103
+ * **`get_pid_by_port(self, port: int) -> int | None`**: A static method that attempts to find
104
+ a process ID (PID) listening on a given `port`.
105
+ * It uses platform-specific commands (`netstat`, `ss`, `lsof`).
106
+ * Returns the PID if found, otherwise `None`.
105
107
 
106
- * **`kill_process(pid: int) -> bool`**: A static method that attempts to kill the process
107
- * with the given `pid`. It uses platform-specific commands (`taskkill`, `kill -9`).
108
+ * **`kill_process(self, pid: int) -> bool`**: A static method that attempts to kill the process
109
+ with the given `pid`.
110
+ * It uses platform-specific commands (`taskkill`, `kill -9`).
108
111
  * Returns `True` if the process was successfully killed, `False` otherwise.
109
112
 
110
- * **`release_all(self, ports: Optional[list[int]] = None) -> None`**: Releases all processes
111
- * listening on the specified `ports`.
112
- * `ports` (`list[int]`, *optional*): A list of ports to release. If `None`, it uses the
113
- * `default_ports` defined during initialization.
114
- * For each port, it first tries to get the PID and then attempts to kill the process.
113
+ * **`release_all(self, ports: list[int] | None = None) -> None`**: Releases all processes listening on the specified `ports`.
114
+ * `ports`: A list of ports to release.
115
+ * If `None`, it uses the `default_ports` defined during initialization.
116
+ * For each port, it first tries to get the PID and then attempts to kill the process.
115
117
  * It logs the actions and any errors encountered. Invalid port numbers in the provided list are skipped.
116
118
 
117
119
  #### Example Usage:
118
120
 
119
121
  ```python
120
- from src.nano_dev_utils.release_ports import PortsRelease
122
+ import logging
123
+ from nano_dev_utils import PortsRelease
124
+
125
+ # in case you're interested in logging
126
+ logging.basicConfig(filename='port release.log',
127
+ level=logging.INFO, # specify here desire level, e.g. DEBUG etc.
128
+ format='%(asctime)s - %(levelname)s: %(message)s',
129
+ datefmt='%d-%m-%Y %H:%M:%S')
121
130
 
122
131
  # Create an instance with default ports
123
132
  port_releaser = PortsRelease()
@@ -10,16 +10,17 @@ This module provides a `Timer` class for measuring the execution time of code bl
10
10
 
11
11
  #### `Timer` Class
12
12
 
13
- * **`__init__(self, precision=4, verbose=False)`**: Initializes a `Timer` instance.
14
- * `precision` (int, optional): The number of decimal places to record and
15
- * display time durations. Defaults to 4.
16
- * `verbose` (bool, optional): If `True`, the function's arguments and keyword
17
- * arguments will be included in the printed timing output. Defaults to `False`.
18
-
19
- * **`timeit(self, func)`**: A decorator that measures the execution time of the decorated function.
20
- * When the decorated function is called, this decorator records the start and end times,
21
- * calculates the total execution time, prints the function name and execution time
22
- * (optionally including arguments if `verbose` is `True`), and returns the result of the original function.
13
+ * **`__init__(self, precision: int = 4, verbose: bool = False)`**: Initializes a `Timer` instance.
14
+ * `precision`: The number of decimal places to record and
15
+ display time durations. Defaults to 4.
16
+ * `verbose`: Optionally displays the function's positional arguments (args) and keyword arguments (kwargs).
17
+ Defaults to `False`.
18
+
19
+ * **`timeit(self, func: Callable[P, R]) -> Callable[P, R]`**:
20
+ Decorator that times function execution with automatic unit scaling.
21
+ * When the decorated function is called, this decorator records the start and end times,
22
+ calculates the total execution time, prints the function name and execution
23
+ time (optionally including arguments), and returns the result of the original function.
23
24
 
24
25
  #### Example Usage:
25
26
 
@@ -52,7 +53,7 @@ This module provides an `Importer` class for lazy loading and caching module imp
52
53
  * **`import_mod_from_lib(self, library: str, module_name: str) -> ModuleType | Any`**: Lazily imports a module from a specified library and caches it.
53
54
  * `library` (str): The name of the library (e.g., "os", "requests").
54
55
  * `module_name` (str): The name of the module to import within the library (e.g., "path", "get").
55
- * Returns the imported module. If the module has already been imported, it returns the cached instance.
56
+ * Returns the imported module. If the module has already been imported, it returns a cached instance.
56
57
  * Raises `ImportError` if the module cannot be found.
57
58
 
58
59
  #### Example Usage:
@@ -76,34 +77,42 @@ print(f"Imported os.path again (cached): {os_path_again}")
76
77
  ### `release_ports.py`
77
78
 
78
79
  This module provides a `PortsRelease` class to identify and release processes
79
- listening on specified TCP ports. It supports Windows, Linux, and macOS.
80
+ listening on specified TCP ports.
81
+ It supports Windows, Linux, and macOS.
80
82
 
81
83
  #### `PortsRelease` Class
82
84
 
83
- * **`__init__(self, default_ports: Optional[list[int]] = None)`**:
85
+ * **`__init__(self, default_ports: list[int] | None = None)`**:
84
86
  * Initializes a `PortsRelease` instance.
85
- * `default_ports` (`list[int]`, *optional*): A list of default ports to manage.
86
- * If not provided, it defaults to `[6277, 6274]`.
87
+ * `default_ports`: A list of default ports to manage. If not provided, it defaults to `[6277, 6274]`.
87
88
 
88
- * **`get_pid_by_port(port: int) -> Optional[int]`**: A static method that attempts to
89
- * find the process ID (PID) listening on the given `port`. It uses platform-specific
90
- * commands (`netstat`, `ss`, `lsof`). Returns the PID if found, otherwise `None`.
89
+ * **`get_pid_by_port(self, port: int) -> int | None`**: A static method that attempts to find
90
+ a process ID (PID) listening on a given `port`.
91
+ * It uses platform-specific commands (`netstat`, `ss`, `lsof`).
92
+ * Returns the PID if found, otherwise `None`.
91
93
 
92
- * **`kill_process(pid: int) -> bool`**: A static method that attempts to kill the process
93
- * with the given `pid`. It uses platform-specific commands (`taskkill`, `kill -9`).
94
+ * **`kill_process(self, pid: int) -> bool`**: A static method that attempts to kill the process
95
+ with the given `pid`.
96
+ * It uses platform-specific commands (`taskkill`, `kill -9`).
94
97
  * Returns `True` if the process was successfully killed, `False` otherwise.
95
98
 
96
- * **`release_all(self, ports: Optional[list[int]] = None) -> None`**: Releases all processes
97
- * listening on the specified `ports`.
98
- * `ports` (`list[int]`, *optional*): A list of ports to release. If `None`, it uses the
99
- * `default_ports` defined during initialization.
100
- * For each port, it first tries to get the PID and then attempts to kill the process.
99
+ * **`release_all(self, ports: list[int] | None = None) -> None`**: Releases all processes listening on the specified `ports`.
100
+ * `ports`: A list of ports to release.
101
+ * If `None`, it uses the `default_ports` defined during initialization.
102
+ * For each port, it first tries to get the PID and then attempts to kill the process.
101
103
  * It logs the actions and any errors encountered. Invalid port numbers in the provided list are skipped.
102
104
 
103
105
  #### Example Usage:
104
106
 
105
107
  ```python
106
- from src.nano_dev_utils.release_ports import PortsRelease
108
+ import logging
109
+ from nano_dev_utils import PortsRelease
110
+
111
+ # in case you're interested in logging
112
+ logging.basicConfig(filename='port release.log',
113
+ level=logging.INFO, # specify here desire level, e.g. DEBUG etc.
114
+ format='%(asctime)s - %(levelname)s: %(message)s',
115
+ datefmt='%d-%m-%Y %H:%M:%S')
107
116
 
108
117
  # Create an instance with default ports
109
118
  port_releaser = PortsRelease()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nano_dev_utils"
3
- version = "0.3.4"
3
+ version = "0.4.1"
4
4
 
5
5
  authors = [
6
6
  { name="Yaron Dayan", email="yaronday77@gmail.com" },
@@ -2,12 +2,8 @@ import platform
2
2
  import subprocess
3
3
  import logging
4
4
 
5
-
6
- logging.basicConfig(filename='port release.log',
7
- level=logging.INFO,
8
- format='%(asctime)s - %(levelname)s: %(message)s',
9
- datefmt='%d-%m-%Y %H:%M:%S')
10
5
  lgr = logging.getLogger(__name__)
6
+ """Module-level logger. Configure using logging.basicConfig() in your application."""
11
7
 
12
8
  PROXY_SERVER = 6277
13
9
  INSPECTOR_CLIENT = 6274
@@ -152,5 +148,4 @@ class PortsRelease:
152
148
  else:
153
149
  lgr.error(self._log_terminate_failed(pid=pid, port=port))
154
150
  except Exception as e:
155
- lgr.error(self._log_unexpected_error(e))
156
-
151
+ lgr.error(self._log_unexpected_error(e))
@@ -0,0 +1,39 @@
1
+ from functools import wraps
2
+ import time
3
+ from typing import Callable, ParamSpec, TypeVar
4
+ P = ParamSpec('P')
5
+ R = TypeVar('R')
6
+
7
+
8
+ class Timer:
9
+ def __init__(self, precision=4, verbose=False):
10
+ self.precision = precision
11
+ self.verbose = verbose
12
+ self.units = [
13
+ (1e9, 's'),
14
+ (1e6, 'ms'),
15
+ (1e3, 'μs'),
16
+ (1.0, 'ns')
17
+ ]
18
+
19
+ def timeit(self, func: Callable[P, R]) -> Callable[P, R]:
20
+ """Decorator that times function execution with automatic unit scaling."""
21
+ @wraps(func)
22
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
23
+ start = time.perf_counter_ns()
24
+ result = func(*args, **kwargs)
25
+ elapsed = time.perf_counter_ns() - start
26
+
27
+ value = elapsed
28
+ unit = 'ns'
29
+
30
+ for divisor, unit in self.units:
31
+ if elapsed >= divisor or unit == 'ns':
32
+ value = elapsed / divisor
33
+ break
34
+
35
+ extra_info = f'{args} {kwargs} ' if self.verbose else ''
36
+ print(f'{func.__name__} {extra_info}took {value:.{self.precision}f} [{unit}]')
37
+ return result
38
+
39
+ return wrapper
@@ -173,12 +173,13 @@ class TestPortsRelease(unittest.TestCase):
173
173
 
174
174
  def test_get_pid_by_port_unexpected_exception(self):
175
175
  with patch('platform.system', return_value='Linux'):
176
- with patch('subprocess.Popen', side_effect=Exception("Unexpected")):
176
+ err = Exception("Unexpected")
177
+ with patch('subprocess.Popen', side_effect=err):
177
178
  port = 1234
178
179
  pid = self.ports_release.get_pid_by_port(port)
179
180
  self.assertIsNone(pid)
180
- self.mock_logger.error.assert_called_once_with("An unexpected "
181
- "error occurred: Unexpected")
181
+ self.mock_logger.error.assert_called_once_with(f'An unexpected '
182
+ f'error occurred: {err}')
182
183
 
183
184
  def test_kill_process_success(self):
184
185
  with patch('platform.system', return_value='Linux'):
@@ -1,35 +0,0 @@
1
- from functools import wraps
2
- import time
3
-
4
-
5
- class Timer:
6
- def __init__(self, precision=4, verbose=False):
7
- self.precision = precision
8
- self.verbose = verbose
9
-
10
- def timeit(self, func):
11
- @wraps(func)
12
- def timeit_wrapper(*args, **kwargs):
13
- start_time = time.perf_counter_ns()
14
- result = func(*args, **kwargs)
15
- end_time = time.perf_counter_ns()
16
- total_ns = end_time - start_time
17
-
18
- if total_ns < 1_000: # 1μs
19
- value = total_ns
20
- unit = "ns"
21
- elif total_ns < 1_000_000: # < 1ms
22
- value = total_ns / 1_000
23
- unit = "μs"
24
- elif total_ns < 1_000_000_000: # < 1s
25
- value = total_ns / 1_000_000
26
- unit = "ms"
27
- else:
28
- value = total_ns / 1_000_000_000
29
- unit = "s"
30
-
31
- extra_info = f'{args} {kwargs} ' if self.verbose else ''
32
- print(f'{func.__name__} {extra_info}took {value:.{self.precision}f} [{unit}]')
33
- return result
34
-
35
- return timeit_wrapper