ptymer 0.0.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.
ptymer-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Caio Lima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ptymer-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.1
2
+ Name: ptymer
3
+ Version: 0.0.1
4
+ Summary: Ptymer is a lightweight Python package designed to help developers manage and monitor the execution time of their code. With features like timers and countdowns, it allows you to precisely measure runtime durations and set time-based triggers. Whether you need to benchmark performance or execute actions at specific intervals, Ptymer provides a simple and efficient solution
5
+ Author-email: Caio Lima <caio-augusto@live.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Caio Lima
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/hyskoniho/ptymer
29
+ Project-URL: Documentation, https://github.com/hyskoniho/ptymer/wiki
30
+ Project-URL: Issues, https://github.com/hyskoniho/ptymer/issues
31
+ Keywords: timer,countdown,hourglass,alarm
32
+ Classifier: Programming Language :: Python
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Requires-Python: >=3.8
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Requires-Dist: psutil>=6.0.0
40
+ Requires-Dist: python-dateutil>=2.9.0.post0
41
+ Requires-Dist: six>=1.16.0
42
+
43
+ <div align="center">
44
+ <h1>PTymer ⏱️</h1>
45
+ <img src="https://img.shields.io/badge/core-python-%2314354C.svg?style=for-the-badge">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
46
+ <img src="https://img.shields.io/badge/status-online-green?style=for-the-badge">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
47
+ <img src="https://img.shields.io/badge/license-MIT-yellow?style=for-the-badge">
48
+ </div>
49
+
50
+ **PTymer** is a Python project that provides insights and actions within the execution of code in a time context. This package includes three main classes: `Timer`, `HourGlass`, and `Alarm`, each with specific functionalities for time monitoring and control. You can find the full description in the [Wiki Page](https://github.com/hyskoniho/ptymer/wiki)
51
+
52
+ ## Index
53
+
54
+ - [Installation](#installation)
55
+ - [Usage](#usage)
56
+ - [Timer](#timer)
57
+ - [HourGlass](#hourglass)
58
+ - [Alarm](#alarm)
59
+ - [Contribution](#contribution)
60
+ - [License](#license)
61
+
62
+ ## Installation
63
+
64
+ PTymer is compatible with Python 3.8 or higher.
65
+ To install, use pip:
66
+
67
+ ```bash
68
+ pip install ptymer
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ #### Timer
74
+ The Timer class is used to measure the execution time of code snippets. It can be instantiated in several ways:
75
+ ##### Normal Instance
76
+ ```python
77
+ from ptymer import Timer
78
+
79
+ tm = Timer().start()
80
+ # Your code here
81
+ tm.stop()
82
+ ```
83
+
84
+ ##### Context Manager
85
+ ```python
86
+ from ptymer import Timer
87
+
88
+ with Timer() as tm:
89
+ # Your code here
90
+ ```
91
+
92
+ ##### Decorator
93
+ ```python
94
+ from ptymer import Timer
95
+
96
+ @Timer()
97
+ def your_function_here():
98
+ ```
99
+
100
+
101
+ #### HourGlass
102
+ The HourGlass class is used to create a countdown timer. After the countdown finishes, it executes a user-defined function.
103
+ ```python
104
+ from ptymer import HourGlass
105
+
106
+ hg = HourGlass(seconds=5, visibility=True, target=print, args=("Hello World",)).start()
107
+ ```
108
+
109
+ *Note:* In the arguments tuple, you need to put a comma at the end to identify it as a tuple if there's only one element.
110
+
111
+ #### Alarm
112
+ The Alarm class takes a list of times and a function. When the algorithm identifies that it has reached one of the times, it executes the defined function.
113
+ ```python
114
+ from ptymer import Alarm
115
+
116
+ alarm = Alarm(schedules=["10:49:00"], target=target, args=(), visibility=True).start()
117
+ ```
118
+
119
+ <br></br>
120
+
121
+ ###### ⚠️ WARNING!
122
+ Due to multiprocessing, it's highly recommended that you safeguard the execution of the main process, when using `HourGlass` and/or `Alarm` instance, with the following statement before your code:
123
+ ```python
124
+ if __name__ == '__main__':
125
+ # your code here
126
+ ```
127
+ Some sample usage:
128
+ ```python
129
+ def foo():
130
+ return True
131
+
132
+ if __name__ == '__main__':
133
+ foo()
134
+ ```
135
+ You can find more information about this issue [here](https://github.com/hyskoniho/ptymer/wiki/Handling-Parallelism).
136
+
137
+ ## Contribution
138
+ Contributions are welcome!!! Feel free to open issues and pull requests on the GitHub repository.
139
+ Pay attention to the [test files](https://github.com/hyskoniho/ptymer/tree/main/tests) content and don't forget to document every change!
140
+ We use Pytest and there's a [workflow](https://github.com/hyskoniho/ptymer/blob/main/.github/workflows/unit_test.yaml) set up on GitHub Actions that you might want to check out.
141
+
142
+ ## License
143
+ This project is licensed under the MIT License. See the [LICENSE](https://github.com/hyskoniho/ptymer/blob/main/LICENSE) file for more details.
ptymer-0.0.1/README.md ADDED
@@ -0,0 +1,101 @@
1
+ <div align="center">
2
+ <h1>PTymer ⏱️</h1>
3
+ <img src="https://img.shields.io/badge/core-python-%2314354C.svg?style=for-the-badge">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
4
+ <img src="https://img.shields.io/badge/status-online-green?style=for-the-badge">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
5
+ <img src="https://img.shields.io/badge/license-MIT-yellow?style=for-the-badge">
6
+ </div>
7
+
8
+ **PTymer** is a Python project that provides insights and actions within the execution of code in a time context. This package includes three main classes: `Timer`, `HourGlass`, and `Alarm`, each with specific functionalities for time monitoring and control. You can find the full description in the [Wiki Page](https://github.com/hyskoniho/ptymer/wiki)
9
+
10
+ ## Index
11
+
12
+ - [Installation](#installation)
13
+ - [Usage](#usage)
14
+ - [Timer](#timer)
15
+ - [HourGlass](#hourglass)
16
+ - [Alarm](#alarm)
17
+ - [Contribution](#contribution)
18
+ - [License](#license)
19
+
20
+ ## Installation
21
+
22
+ PTymer is compatible with Python 3.8 or higher.
23
+ To install, use pip:
24
+
25
+ ```bash
26
+ pip install ptymer
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ #### Timer
32
+ The Timer class is used to measure the execution time of code snippets. It can be instantiated in several ways:
33
+ ##### Normal Instance
34
+ ```python
35
+ from ptymer import Timer
36
+
37
+ tm = Timer().start()
38
+ # Your code here
39
+ tm.stop()
40
+ ```
41
+
42
+ ##### Context Manager
43
+ ```python
44
+ from ptymer import Timer
45
+
46
+ with Timer() as tm:
47
+ # Your code here
48
+ ```
49
+
50
+ ##### Decorator
51
+ ```python
52
+ from ptymer import Timer
53
+
54
+ @Timer()
55
+ def your_function_here():
56
+ ```
57
+
58
+
59
+ #### HourGlass
60
+ The HourGlass class is used to create a countdown timer. After the countdown finishes, it executes a user-defined function.
61
+ ```python
62
+ from ptymer import HourGlass
63
+
64
+ hg = HourGlass(seconds=5, visibility=True, target=print, args=("Hello World",)).start()
65
+ ```
66
+
67
+ *Note:* In the arguments tuple, you need to put a comma at the end to identify it as a tuple if there's only one element.
68
+
69
+ #### Alarm
70
+ The Alarm class takes a list of times and a function. When the algorithm identifies that it has reached one of the times, it executes the defined function.
71
+ ```python
72
+ from ptymer import Alarm
73
+
74
+ alarm = Alarm(schedules=["10:49:00"], target=target, args=(), visibility=True).start()
75
+ ```
76
+
77
+ <br></br>
78
+
79
+ ###### ⚠️ WARNING!
80
+ Due to multiprocessing, it's highly recommended that you safeguard the execution of the main process, when using `HourGlass` and/or `Alarm` instance, with the following statement before your code:
81
+ ```python
82
+ if __name__ == '__main__':
83
+ # your code here
84
+ ```
85
+ Some sample usage:
86
+ ```python
87
+ def foo():
88
+ return True
89
+
90
+ if __name__ == '__main__':
91
+ foo()
92
+ ```
93
+ You can find more information about this issue [here](https://github.com/hyskoniho/ptymer/wiki/Handling-Parallelism).
94
+
95
+ ## Contribution
96
+ Contributions are welcome!!! Feel free to open issues and pull requests on the GitHub repository.
97
+ Pay attention to the [test files](https://github.com/hyskoniho/ptymer/tree/main/tests) content and don't forget to document every change!
98
+ We use Pytest and there's a [workflow](https://github.com/hyskoniho/ptymer/blob/main/.github/workflows/unit_test.yaml) set up on GitHub Actions that you might want to check out.
99
+
100
+ ## License
101
+ This project is licensed under the MIT License. See the [LICENSE](https://github.com/hyskoniho/ptymer/blob/main/LICENSE) file for more details.
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=40.8.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ptymer"
7
+ version = "0.0.1"
8
+ authors = [{ name="Caio Lima", email="caio-augusto@live.com" }]
9
+ license = { file = "LICENSE" }
10
+ description = "Ptymer is a lightweight Python package designed to help developers manage and monitor the execution time of their code. With features like timers and countdowns, it allows you to precisely measure runtime durations and set time-based triggers. Whether you need to benchmark performance or execute actions at specific intervals, Ptymer provides a simple and efficient solution"
11
+ readme = "README.md"
12
+ requires-python = ">=3.8"
13
+ classifiers = [
14
+ "Programming Language :: Python",
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ keywords = ["timer", "countdown", "hourglass", "alarm"]
20
+ dependencies = [
21
+ "psutil>=6.0.0",
22
+ "python-dateutil>=2.9.0.post0",
23
+ "six>=1.16.0"
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/hyskoniho/ptymer"
28
+ Documentation = "https://github.com/hyskoniho/ptymer/wiki"
29
+ Issues = "https://github.com/hyskoniho/ptymer/issues"
ptymer-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from ._hourglass import HourGlass
2
+ from ._timer import Timer
3
+ from ._alarm import Alarm
4
+
5
+ __all__ = ["HourGlass", "Timer", "Alarm"]
@@ -0,0 +1,273 @@
1
+ from datetime import datetime
2
+ from typing import Callable, Any, List, Tuple, Union, Optional
3
+ from dataclasses import dataclass
4
+ from dateutil import parser
5
+ from psutil import Process as psProcess, pid_exists
6
+ from multiprocessing import Process, freeze_support
7
+
8
+ @dataclass
9
+ class Alarm():
10
+ schedules: List[Union[datetime, str, Tuple[int, int, int, int, int, int, int]]]
11
+ # list of datetime objects
12
+ target: Optional[Callable] = None
13
+ # function that will be executed when the alarm is triggered
14
+ args: Optional[Tuple[Any]] = None
15
+ # arguments of the function
16
+ visibility: bool = False
17
+ # defines if the alarm will show messages or not
18
+ keep_schedules: bool = False
19
+ # if true, the alarm will not be elimnated after being triggered
20
+ __pid: Optional[int] = None
21
+ # process id of the alarm
22
+ __process: Optional[Process] = None
23
+ # process object of the alarm
24
+
25
+ def __post_init__(self) -> None:
26
+ """
27
+ Post-initialization method.
28
+
29
+ This method validates the attributes and converts string representations of dates
30
+ to `datetime` objects. It ensures that the schedules, target function, arguments,
31
+ and other attributes are correctly defined and of the proper types.
32
+
33
+ Raises:
34
+ TypeError: If `schedules` is not a list, if `target` is not a callable function,
35
+ if `args` is not a tuple, if `visibility` is not a boolean, if `keep_schedules`
36
+ is not a boolean, or if any schedule entry is not a valid date.
37
+ ValueError: If `schedules` is empty, if `target` is not defined, or if `args`
38
+ are defined without a target function.
39
+
40
+ Notes:
41
+ - `schedules` should be a list of dates in `datetime`, `tuple`, or `str` format.
42
+ - `target` should be a callable function.
43
+ - `args` should be a tuple of arguments for the target function.
44
+ - `visibility` and `keep_schedules` should be boolean values.
45
+ - The method converts string dates to `datetime` objects and tuple dates to
46
+ `datetime` objects, truncating microseconds for `datetime` objects.
47
+ """
48
+ if not isinstance(self.schedules, list):
49
+ raise TypeError("Schedules must be a list!")
50
+ elif not self.schedules:
51
+ raise ValueError("Schedules must be defined!")
52
+ elif self.target and not isinstance(self.target, Callable):
53
+ raise TypeError("Target must be a function!")
54
+ elif self.args and not isinstance(self.args, tuple):
55
+ raise TypeError("Arguments must be a tuple!")
56
+ elif self.args and not self.target:
57
+ raise ValueError(f"Arguments cannot be defined without a target function!")
58
+ elif not isinstance(self.visibility, bool):
59
+ raise TypeError("Visibility must be a boolean!")
60
+ elif not isinstance(self.keep_schedules, bool):
61
+ raise TypeError("keep_schedules must be a boolean!")
62
+ else:
63
+ for idx, date_obj in enumerate(self.schedules):
64
+ try:
65
+ assert (type(date_obj) in [datetime, tuple, str])
66
+ if type(date_obj) == str:
67
+ self.schedules[idx] = parser.parse(date_obj)
68
+ elif type(date_obj) == tuple:
69
+ self.schedules[idx] = datetime(*date_obj)
70
+ # converting tuple(d, h, m, s) to datetime object
71
+ elif type(date_obj) == datetime:
72
+ self.schedules[idx] = date_obj.replace(microsecond=0)
73
+ # truncating microseconds
74
+ except:
75
+ raise TypeError(f"Invalid datetime object: {date_obj}")
76
+ finally:
77
+ pass
78
+
79
+ def __str__(self) -> str:
80
+ return f"Class Alarm()\nVisibility: {self.visibility}\nSchedules: {self.args}\nKeep_schedules: {self.keep_schedules}\nProcess id: {self.__pid if self.status else None}"
81
+
82
+ def start(self) -> "Alarm":
83
+ """
84
+ Set up the alarm.
85
+
86
+ This method initializes and starts the alarm process. It checks if an alarm is already set and
87
+ raises an error if so. Otherwise, it starts a new alarm process that runs in the background.
88
+
89
+ Returns:
90
+ Alarm: The current instance of the `Alarm` class.
91
+
92
+ Raises:
93
+ ValueError: If an alarm is already set.
94
+
95
+ Notes:
96
+ - This method uses `freeze_support()` to ensure compatibility with Windows.
97
+ - The alarm process is started as a daemon process.
98
+ """
99
+ from os import getpid
100
+
101
+ freeze_support()
102
+ # Freeze support for windows
103
+
104
+ if self.status:
105
+ raise RuntimeError("Alarm already set!")
106
+ else:
107
+ process = Process(target=self._alarm_loop, args=(getpid(),), daemon=True)
108
+ process.start()
109
+ self.__pid = process.pid
110
+ self.__process = process
111
+
112
+ print("Alarm started!") if self.visibility else None
113
+ return self
114
+
115
+ @staticmethod
116
+ def _run_function(target, args, visibility) -> any:
117
+ """
118
+ Execute the stored function with its arguments.
119
+
120
+ This method attempts to run the function stored in `self.target` with the arguments stored in `self.args`.
121
+ If no arguments are provided, the function is called without arguments.
122
+
123
+ Returns:
124
+ any: The return value of the executed function, or `None` if no function is stored. If an exception occurs,
125
+ it returns the exception message as a string.
126
+
127
+ Raises:
128
+ Exception: If an error occurs during the function execution, the exception is caught and its message is printed
129
+
130
+ Notes:
131
+ - If `self.target` is `None`, the method returns `None`.
132
+ - If `self.args` is `None`, the function is called without arguments.
133
+ """
134
+ try:
135
+ if target and args:
136
+ value = target(*args)
137
+ elif target and not args:
138
+ value = target()
139
+ else:
140
+ value = None
141
+ except Exception as e:
142
+ print(f"Error ocurred:\n{e}")
143
+ return str(e)
144
+ else:
145
+ return value
146
+
147
+ def _alarm_loop(self, mainPid: int) -> None:
148
+ """
149
+ Run the alarm loop.
150
+
151
+ This function monitors the schedules and triggers the alarm at the specified times.
152
+ It suspends and resumes the main process around the execution of the alarm function.
153
+
154
+ Args:
155
+ mainPid (int): The process ID of the main process to be monitored.
156
+
157
+ Returns:
158
+ None
159
+
160
+ Raises:
161
+ ValueError: If `mainPid` is not a valid process ID.
162
+
163
+ Notes:
164
+ - The function continuously checks the current time against scheduled alarm times.
165
+ - When the current time matches a scheduled time, the main process is suspended, the alarm function is executed, and then the main process is resumed.
166
+ - If `self.keep_schedules` is `False`, the schedule is removed after the alarm is triggered.
167
+ - The function stops running when there are no more schedules or if the main process no longer exists.
168
+ """
169
+ process = psProcess(mainPid)
170
+ lastIdx = None
171
+
172
+ while len(self.schedules) > 0:
173
+ now = datetime.now().replace(microsecond=0)
174
+ try:
175
+ idx = self.schedules.index(now)
176
+ except:
177
+ continue # if the current time is not in the schedules, it continues the loop
178
+ else:
179
+ if idx != lastIdx: # this is due the speed of the loop, sometimes it triggers the same alarm twice (or much more)
180
+ print("Alarm triggered!") if self.visibility else None
181
+
182
+ process.suspend()
183
+ self._run_function(self.target, self.args, self.visibility)
184
+ process.resume()
185
+
186
+ lastIdx = idx
187
+ if not self.keep_schedules:
188
+ self.schedules.pop(idx)
189
+ self.__pid = None
190
+
191
+ def stop(self) -> None:
192
+ """
193
+ Stop the alarm.
194
+
195
+ This method terminates the alarm process if it is currently running. If no alarm is set,
196
+ it raises an error.
197
+
198
+ Raises:
199
+ RuntimeError: If no alarm is currently set.
200
+
201
+ Notes:
202
+ - The method checks if the alarm process exists and terminates it if so.
203
+ - If `self.visibility` is `True`, it prints a message indicating that the alarm has stopped.
204
+ """
205
+ if self.status:
206
+ process = psProcess(self.__pid)
207
+ process.terminate()
208
+ self.__pid = None
209
+ self.__process = None
210
+ else:
211
+ raise RuntimeError("Alarm not set!")
212
+
213
+ print("Alarm stopped!") if self.visibility else None
214
+
215
+ @property
216
+ def pid(self) -> int:
217
+ """
218
+ Get the process ID.
219
+
220
+ This method returns the process ID of the running alarm process. If no alarm process
221
+ is running, it raises an error.
222
+
223
+ Returns:
224
+ int: The process ID of the running alarm.
225
+
226
+ Raises:
227
+ AttributeError: If no alarm process is currently running.
228
+
229
+ Notes:
230
+ - The method checks if the alarm process ID is set and if the process exists.
231
+ """
232
+ if not self.status:
233
+ raise AttributeError(f"There is no alarm running!")
234
+ else:
235
+ return int(self.__pid)
236
+
237
+ @property
238
+ def status(self) -> bool:
239
+ """
240
+ Check if the alarm is active.
241
+
242
+ This method checks if the alarm process is currently running.
243
+
244
+ Returns:
245
+ bool: `True` if the alarm process is active, `False` otherwise.
246
+
247
+ Notes:
248
+ - The method checks if the process ID is set and if the process exists.
249
+ """
250
+ if self.__pid and pid_exists(self.__pid):
251
+ return True
252
+ else:
253
+ return False
254
+
255
+ def wait(self) -> None:
256
+ """
257
+ Wait for the alarm to finish.
258
+
259
+ This method waits for the alarm process to finish before returning.
260
+
261
+ Returns:
262
+ None
263
+
264
+ Notes:
265
+ - The method uses the `join()` method of the alarm process.
266
+ """
267
+ if self.status:
268
+ self.__process.join()
269
+ else:
270
+ raise RuntimeError("Alarm not set!")
271
+
272
+ if __name__ == "__main__":
273
+ pass