lukhed-basic-utils 0.2.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 lukhed
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.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.1
2
+ Name: lukhed_basic_utils
3
+ Version: 0.2.0
4
+ Summary: A collection of basic utility functions
5
+ Home-page: https://github.com/lukhed/lukhed_basic_utils
6
+ Author: lukhed
7
+ Author-email: lukhed.mail@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # aceCommon
16
+
17
+ A collection of basic utility functions for Python projects.
18
+
19
+ ## Installation and Usage
20
+
21
+ ```bash
22
+ pip install lukhed_basic_utils
23
+ from lukhed_basic_utils import create_file_path_string
24
+
25
+ example_path = create_file_path_string(list_of_dir=['subdir', 'file.txt'])
26
+
27
+ print(path)
28
+ ```
@@ -0,0 +1,14 @@
1
+ # aceCommon
2
+
3
+ A collection of basic utility functions for Python projects.
4
+
5
+ ## Installation and Usage
6
+
7
+ ```bash
8
+ pip install lukhed_basic_utils
9
+ from lukhed_basic_utils import create_file_path_string
10
+
11
+ example_path = create_file_path_string(list_of_dir=['subdir', 'file.txt'])
12
+
13
+ print(path)
14
+ ```
@@ -0,0 +1 @@
1
+ from .osCommon import create_file_path_string, append_to_dir
@@ -0,0 +1,402 @@
1
+ from lukhed_basic_utils import osCommon as osC
2
+ from lukhed_basic_utils import fileCommon as fC
3
+ from lukhed_basic_utils import timeCommon as tC
4
+
5
+ class AceLogging:
6
+ """
7
+ Provides basic logging functionality for classes, writing logs to a JSON file.
8
+
9
+ Logs are structured hierarchically by date, with each run assigned a unique ID.
10
+ By default, logs are stored in the directory `logs/projectLogs.json`.
11
+
12
+ Attributes:
13
+ ace_logging (bool): Whether logging is enabled.
14
+ ace_log_directory (str): The directory where the log file will be stored.
15
+ ace_log_file_path (str): Full path to the log file.
16
+ ace_log_time_stamp (str): Timestamp for the current date in "%Y%m%d" format.
17
+ log_run_id (int): ID for the current run.
18
+
19
+ Methods:
20
+ logging_add_event: Adds an event log to the current run.
21
+ logging_print_active_log: Prints all events logged during the current run.
22
+ print_log_items_by_specified_tag: Filters and prints logs by a specified tag (e.g., "ERROR").
23
+
24
+ Example Usage:
25
+ An example of logging in a custom class:
26
+
27
+ ```python
28
+ class MyCustomClass(AceLogging):
29
+ def __init__(self, logging=True):
30
+ AceLogging.__init__(self, logging=logging)
31
+
32
+ def example_custom_method(self):
33
+ test = "do some stuff"
34
+ # some error occurs you want to log
35
+ self.logging_add_event(log_type_named="error", function_named="example_custom_method",
36
+ short_description="A test to check logging functionality"
37
+ any_content={"someData": "you want to save"})
38
+ ```
39
+ """
40
+ def __init__(self, log_directory=None, logging=False, custom_file_name=None):
41
+ self.ace_logging = logging
42
+ if self.ace_logging:
43
+ if custom_file_name is None:
44
+ self.ace_logging_fn = "projectLogs.json"
45
+ else:
46
+ self.ace_logging_fn = custom_file_name
47
+
48
+ if log_directory is None:
49
+ self.ace_log_directory = osC.create_file_path_string(["logs"])
50
+ else:
51
+ self.ace_log_directory = log_directory
52
+
53
+ self.ace_log_file_path = osC.append_to_dir(self.ace_log_directory, self.ace_logging_fn)
54
+ self.ace_log_time_stamp = tC.get_today_date(convert_to_string_format="%Y%m%d")
55
+ self._check_create_dir_structure()
56
+
57
+ def _check_create_dir_structure(self):
58
+ if not osC.check_if_dir_exists(self.ace_log_directory):
59
+ osC.create_dir(self.ace_log_directory)
60
+ if not osC.check_if_file_exists(self.ace_log_file_path):
61
+ self.ace_active_log = {self.ace_log_time_stamp: {"totalRuns": 1, "eventsLogged": []}}
62
+ fC.dump_json_to_file(self.ace_log_file_path, self.ace_active_log)
63
+ self.log_run_id = 1
64
+ else:
65
+ self.ace_active_log = fC.load_json_from_file(self.ace_log_file_path)
66
+ try:
67
+ tr = self.ace_active_log[self.ace_log_time_stamp]["totalRuns"]
68
+ tr = tr + 1
69
+ self.log_run_id = tr
70
+ self.ace_active_log[self.ace_log_time_stamp]["totalRuns"] = tr
71
+ self._write_to_log()
72
+ except KeyError:
73
+ self.ace_active_log.update({self.ace_log_time_stamp: {}})
74
+ self.ace_active_log[self.ace_log_time_stamp] = {"totalRuns": 1, "eventsLogged": []}
75
+ self.log_run_id = 1
76
+ self._write_to_log()
77
+
78
+ def _write_to_log(self):
79
+ fC.dump_json_to_file(self.ace_log_file_path, self.ace_active_log)
80
+
81
+
82
+ def logging_add_event(self, log_type_named=None, function_named=None, short_description=None,
83
+ any_content=None):
84
+ """
85
+ Adds an event to the log file for the current run.
86
+
87
+ Parameters:
88
+ log_type_named (str, optional): The type of log (e.g., "ERROR", "INFO").
89
+ function_named (str, optional): The name of the function generating the log.
90
+ short_description (str, optional): A brief description of the event.
91
+ any_content (dict, optional): Additional details related to the event.
92
+
93
+ Returns:
94
+ None
95
+ """
96
+
97
+ if self.ace_logging:
98
+ if log_type_named is None:
99
+ pass
100
+ else:
101
+ log_type_named = log_type_named.upper()
102
+
103
+ log_event = {
104
+ "runNumber": self.log_run_id,
105
+ "timeLogged": tC.create_timestamp(output_format="%Y%m%d%H%M%S"),
106
+ "logType": log_type_named,
107
+ "functionName": function_named,
108
+ "description": short_description,
109
+ "details": any_content
110
+ }
111
+
112
+ self.ace_active_log[self.ace_log_time_stamp]["eventsLogged"].append(log_event)
113
+ self._write_to_log()
114
+
115
+ def logging_print_active_log(self):
116
+ print("\n****** All Log Events for Current Run ******")
117
+ current_log = [x for x in self.ace_active_log[self.ace_log_time_stamp]["eventsLogged"]
118
+ if x["runNumber"] == self.log_run_id]
119
+ count = 1
120
+ for log in current_log:
121
+ print("\nEvent = " + str(count))
122
+ print(log['logType'] + "," + log['timeLogged'] + "," + log["functionName"])
123
+ print(log["description"])
124
+ print(log["details"])
125
+ count = count + 1
126
+
127
+
128
+ def print_log_items_by_specified_tag(self, tag="ERROR", printing=True, last_run_only=True, spec_date=None,
129
+ date_format="%Y%m%d", headless_mode=False):
130
+ """
131
+ Prints or retrieves logs filtered by a specific tag (e.g., "ERROR").
132
+
133
+ Parameters:
134
+ tag (str): The tag to filter logs (default is "ERROR").
135
+ printing (bool): If True, logs will be printed; otherwise, results are returned.
136
+ last_run_only (bool): If True, filters logs from the most recent run only.
137
+ spec_date (str, optional): Specific date to filter logs (format specified by `date_format`).
138
+ date_format (str): Format of the provided date (default is "%Y%m%d").
139
+ headless_mode (bool): If True, disables interactive prompts and returns results as a list.
140
+
141
+ Returns:
142
+ list: Filtered logs matching the specified criteria.
143
+ """
144
+
145
+ log = fC.load_json_from_file(self.ace_log_file_path)
146
+
147
+ if last_run_only:
148
+ last_date = {}
149
+ for key in log:
150
+ last_date = log[key]
151
+
152
+ total_runs = last_date["totalRuns"]
153
+ specified_logs = [x for x in last_date["eventsLogged"] if x["runNumber"] == total_runs]
154
+ elif spec_date is not None:
155
+ date_key = tC.convert_date_format(spec_date, from_format=date_format, to_format="%Y%m%d")
156
+ specified_logs = log[date_key]["eventsLogged"]
157
+ else:
158
+ specified_logs = []
159
+ for key in log:
160
+ specified_logs.extend(log[key]["eventsLogged"])
161
+
162
+ found_logs = [x for x in specified_logs if x["logType"].lower() == tag.lower()]
163
+
164
+
165
+ if printing:
166
+ for temp_log in found_logs:
167
+ print("Time= " + temp_log["timeLogged"])
168
+ print("Function Name= " + temp_log["functionName"])
169
+ print("Description= " + temp_log["description"])
170
+ print("\n")
171
+
172
+ if headless_mode is False:
173
+ input("Press any key to close")
174
+
175
+ return found_logs
176
+
177
+
178
+ class AceJobScheduler(AceLogging):
179
+ """
180
+ Extends `AceLogging` to include job scheduling functionality.
181
+
182
+ This class tracks and manages job execution based on various scheduling requirements
183
+ (e.g., daily, weekly, after a certain time).
184
+
185
+ Attributes:
186
+ ace_job_scheduler_date (str): Current date in "%Y%m%d" format.
187
+ ace_job_scheduler_fn (str): Name of the job status file.
188
+
189
+ Methods:
190
+ _check_if_job_needs_running: Determines whether a job should run based on the provided schedule.
191
+ _write_job_status: Updates the job status file after execution.
192
+ """
193
+ def __init__(self, log_setting=True, job_status_file_name="jobStatus.json"):
194
+ AceLogging.__init__(self, logging=log_setting)
195
+ self.ace_job_scheduler_date = tC.get_today_date(convert_to_string_format="%Y%m%d")
196
+ self.ace_job_scheduler_fn = job_status_file_name
197
+
198
+ def _create_new_job_status_file_for_job(self, job_dir):
199
+ job_status = {
200
+ "lastRunDate": None,
201
+ "lastRunDay": None,
202
+ "lastRunHour": None,
203
+ "lastRunMinute": None,
204
+ "lastRunSecond": None,
205
+ "lastRunSuccess": None,
206
+ "lastRunMonth": None,
207
+ "lastRunDayName": None,
208
+ "lastRunTimeStamp": None
209
+ }
210
+
211
+ new_file = osC.append_to_dir(job_dir, self.ace_job_scheduler_fn)
212
+ fC.dump_json_to_file(new_file, job_status)
213
+ self.logging_add_event("info", "_create_job_status_file_for_job", "created new job status file",
214
+ {"file": new_file})
215
+
216
+ def _get_job_status_json(self, job_dir):
217
+ osC.check_create_dir_structure(job_dir, full_path=True)
218
+ job_status_file = osC.append_to_dir(job_dir, self.ace_job_scheduler_fn)
219
+ if not osC.check_if_file_exists(job_status_file):
220
+ self._create_new_job_status_file_for_job(job_dir)
221
+ return fC.load_json_from_file(job_status_file)
222
+
223
+ def _check_if_job_needs_running(self, job_dir, requirement="daily", after_hour=0):
224
+ """
225
+ Determines whether a job should run based on the given requirement.
226
+
227
+ Parameters:
228
+ job_dir (str): Directory for the job status file.
229
+ requirement (str): Scheduling requirement, options include:
230
+ - "daily": Runs once per day.
231
+ - "hourly": Runs once per hour.
232
+ - "weekly": Runs once per week.
233
+ - A specific day (e.g., "monday").
234
+ - A list of days (e.g., ["monday", "wednesday"]).
235
+ - "X min": Runs if more than X minutes have passed since the last run.
236
+ after_hour (int): The earliest hour (in 24-hour format) at which the job can run.
237
+
238
+ Returns:
239
+ bool: True if the job should run, False otherwise.
240
+ """
241
+
242
+ requirement = requirement.lower()
243
+ job_status = self._get_job_status_json(job_dir)
244
+ cur_ts = tC.create_timestamp(output_format="%Y%m%d%H%M%S")
245
+ time_components = tC.extract_date_time_components(cur_ts, input_format="%Y%m%d%H%M%S")
246
+ {'year': 2024, 'month': 12, 'day': 22, 'hour': 15, 'minute': 30, 'second': 45}
247
+ month = time_components['month']
248
+ day = time_components['day']
249
+ hour = time_components['hour']
250
+ minute = time_components['minute']
251
+ second = time_components['second']
252
+
253
+ # Must be greater than or equal to after hour to need a run. Need to check this first.
254
+ if hour < after_hour:
255
+ self.logging_add_event("info", "_check_if_job_needs_running",
256
+ "determined job does not need to run: after hour requirement", {"job": job_dir})
257
+ return False
258
+
259
+ # check daily related requirements
260
+ days_of_week = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
261
+ if type(requirement) is list:
262
+ temp_flag = False
263
+ today = tC.get_current_day().lower()
264
+ for req in requirement:
265
+ if req in days_of_week:
266
+ if today != requirement:
267
+ pass
268
+ else:
269
+ temp_flag = True
270
+
271
+ if temp_flag:
272
+ pass
273
+ else:
274
+ self.logging_add_event("info", "_check_if_job_needs_running",
275
+ "determined job does not need to run: daily requirement",
276
+ {"job": job_dir})
277
+ return False
278
+ else:
279
+ if requirement in days_of_week:
280
+ today = tC.get_current_day().lower()
281
+ if today != requirement:
282
+ self.logging_add_event("info", "_check_if_job_needs_running",
283
+ "determined job does not need to run: daily requirement",
284
+ {"job": job_dir})
285
+ return False
286
+
287
+
288
+ # If got here, then day and hour is satisfied. So if last run is None, need to run.
289
+ success = job_status["lastRunSuccess"]
290
+ if job_status["lastRunDate"] is None:
291
+ self.logging_add_event("info", "_check_if_job_needs_running",
292
+ "determined job needs to run: no job status data", {"job": job_dir})
293
+ return True
294
+ else:
295
+ if success is False or success is None:
296
+ return True
297
+ else:
298
+ pass
299
+
300
+
301
+ """
302
+ If reached to this point, the job is not disqualified yet based on requirements.
303
+ All code above looked for disqualifying items based on day or week and after hour.
304
+ Now check if the code should run given the requirement.
305
+
306
+ For example: it may be the correct day to run the code per the requirement, but it may have already
307
+ been run on the day.
308
+ """
309
+ if requirement == "daily" or requirement in days_of_week:
310
+ if tC.get_today_date(convert_to_string_format="%Y%m%d") == job_status["lastRunDate"]:
311
+ self.logging_add_event("info", "_check_if_job_needs_running",
312
+ "determined job does not need to run: daily requirement", {"job": job_dir})
313
+ return False
314
+ else:
315
+ self.logging_add_event("info", "_check_if_job_needs_running",
316
+ "determined job needs to run: daily requirement", {"job": job_dir})
317
+ return True
318
+ elif requirement == "hourly":
319
+ if tC.get_today_date(convert_to_string_format="%Y%m%d") == job_status["lastRunDate"]:
320
+ # Day is today, so check hourly
321
+ last_hour_ran = int(job_status["lastRunHour"])
322
+ if hour == last_hour_ran:
323
+ self.logging_add_event("info", "_check_if_job_needs_running",
324
+ "determined job does not need to run: daily requirement", {"job": job_dir})
325
+ return False
326
+ else:
327
+ self.logging_add_event("info", "_check_if_job_needs_running",
328
+ "determined job needs to run: daily requirement", {"job": job_dir})
329
+ return True
330
+ else:
331
+ # If job has not run in the new day yet, by definition, job needs to run.
332
+ self.logging_add_event("info", "_check_if_job_needs_running",
333
+ "determined job needs to run: daily requirement", {"job": job_dir})
334
+ return True
335
+ elif requirement == "weekly":
336
+ weekly = job_status["lastRunDate"]
337
+ if weekly is None:
338
+ self.logging_add_event("info", "_check_if_job_needs_running",
339
+ "determined job needs to run: weekly requirement", {"job": job_dir})
340
+ return True
341
+
342
+ temp_year = weekly[:4]
343
+ cur_year = tC.get_current_year()
344
+
345
+ if temp_year != cur_year:
346
+ self.logging_add_event("info", "_check_if_job_needs_running",
347
+ "determined job needs to run: weekly requirement", {"job": job_dir})
348
+ return True
349
+
350
+ wk_int_last_ran = tC.get_week_number_for_date(provided_date=weekly, provided_date_format="%Y%m%d")
351
+ wk_int_today = tC.get_week_number_for_date()
352
+
353
+ if wk_int_today == wk_int_last_ran:
354
+ self.logging_add_event("info", "_check_if_job_needs_running",
355
+ "determined job does not need to run: weekly requirement", {"job": job_dir})
356
+ return False
357
+ else:
358
+ self.logging_add_event("info", "_check_if_job_needs_running",
359
+ "determined job needs to run: weekly requirement", {"job": job_dir})
360
+ return True
361
+ elif "min" in requirement:
362
+ diff_req = int(requirement.split(" ")[0])
363
+ diff_act = tC.subtract_time_stamps(job_status["lastRunTimeStamp"], cur_ts)["minutes"]
364
+ if diff_act >= diff_req:
365
+ self.logging_add_event("info", "_check_if_job_needs_running",
366
+ "determined job needs to run: minutes requirement", {"job": job_dir})
367
+ return True
368
+ else:
369
+ self.logging_add_event("info", "_check_if_job_needs_running",
370
+ "determined job does not need to run: minutes requirement", {"job": job_dir})
371
+ return False
372
+
373
+ else:
374
+ self.logging_add_event("info", "_check_if_job_needs_running",
375
+ "determined job does not need to run: no valid requirement", {"job": job_dir})
376
+ return False
377
+
378
+ def _write_job_status(self, job_dir, success_bool):
379
+ job_status_file_path = osC.append_to_dir(job_dir, "jobStatus.json")
380
+ cur_ts = tC.create_timestamp(output_format="%Y%m%d%H%M%S")
381
+ time_components = tC.extract_date_time_components(cur_ts, input_format="%Y%m%d%H%M%S")
382
+ month = time_components['month']
383
+ day = time_components['day']
384
+ hour = time_components['hour']
385
+ minute = time_components['minute']
386
+ second = time_components['second']
387
+
388
+ job_status = {
389
+ "lastRunDate": tC.get_today_date(convert_to_string_format="%Y%m%d"),
390
+ "lastRunHour": hour,
391
+ "lastRunMinute": minute,
392
+ "lastRunSecond": second,
393
+ "lastRunSuccess": success_bool,
394
+ "lastRunMonth": month,
395
+ "lastRunDay": day,
396
+ "lastRunDayName": tC.get_current_day(),
397
+ "lastRunTimeStamp": tC.create_timestamp(output_format="%Y%m%d%H%M%S")
398
+ }
399
+
400
+ fC.dump_json_to_file(job_status_file_path, job_status)
401
+ self.logging_add_event("info", "_write_job_status", "updated job status",
402
+ {"job": job_dir, "success": success_bool})