halib 0.1.7__py3-none-any.whl → 0.1.99__py3-none-any.whl

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 (55) hide show
  1. halib/__init__.py +84 -0
  2. halib/common.py +151 -0
  3. halib/cuda.py +39 -0
  4. halib/dataset.py +209 -0
  5. halib/filetype/csvfile.py +151 -45
  6. halib/filetype/ipynb.py +63 -0
  7. halib/filetype/jsonfile.py +1 -1
  8. halib/filetype/textfile.py +4 -4
  9. halib/filetype/videofile.py +44 -33
  10. halib/filetype/yamlfile.py +95 -0
  11. halib/gdrive.py +1 -1
  12. halib/online/gdrive.py +104 -54
  13. halib/online/gdrive_mkdir.py +29 -17
  14. halib/online/gdrive_test.py +31 -18
  15. halib/online/projectmake.py +58 -43
  16. halib/plot.py +296 -11
  17. halib/projectmake.py +1 -1
  18. halib/research/__init__.py +0 -0
  19. halib/research/base_config.py +100 -0
  20. halib/research/base_exp.py +100 -0
  21. halib/research/benchquery.py +131 -0
  22. halib/research/dataset.py +208 -0
  23. halib/research/flop_csv.py +34 -0
  24. halib/research/flops.py +156 -0
  25. halib/research/metrics.py +133 -0
  26. halib/research/mics.py +68 -0
  27. halib/research/params_gen.py +108 -0
  28. halib/research/perfcalc.py +336 -0
  29. halib/research/perftb.py +780 -0
  30. halib/research/plot.py +758 -0
  31. halib/research/profiler.py +300 -0
  32. halib/research/torchloader.py +162 -0
  33. halib/research/wandb_op.py +116 -0
  34. halib/rich_color.py +285 -0
  35. halib/sys/filesys.py +17 -10
  36. halib/system/__init__.py +0 -0
  37. halib/system/cmd.py +8 -0
  38. halib/system/filesys.py +124 -0
  39. halib/tele_noti.py +166 -0
  40. halib/torchloader.py +162 -0
  41. halib/utils/__init__.py +0 -0
  42. halib/utils/dataclass_util.py +40 -0
  43. halib/utils/dict_op.py +9 -0
  44. halib/utils/gpu_mon.py +58 -0
  45. halib/utils/listop.py +13 -0
  46. halib/utils/tele_noti.py +166 -0
  47. halib/utils/video.py +82 -0
  48. halib/videofile.py +1 -1
  49. halib-0.1.99.dist-info/METADATA +209 -0
  50. halib-0.1.99.dist-info/RECORD +64 -0
  51. {halib-0.1.7.dist-info → halib-0.1.99.dist-info}/WHEEL +1 -1
  52. halib-0.1.7.dist-info/METADATA +0 -59
  53. halib-0.1.7.dist-info/RECORD +0 -30
  54. {halib-0.1.7.dist-info → halib-0.1.99.dist-info/licenses}/LICENSE.txt +0 -0
  55. {halib-0.1.7.dist-info → halib-0.1.99.dist-info}/top_level.txt +0 -0
halib/rich_color.py ADDED
@@ -0,0 +1,285 @@
1
+ from rich.console import Console
2
+ from rich.pretty import pprint
3
+ from rich.table import Table
4
+ from rich.text import Text
5
+ from rich.panel import Panel
6
+
7
+ # List of colors
8
+ # ! https://rich.readthedocs.io/en/stable/appendix/colors.html
9
+ all_colors = [
10
+ "black",
11
+ "red",
12
+ "green",
13
+ "yellow",
14
+ "blue",
15
+ "magenta",
16
+ "cyan",
17
+ "white",
18
+ "bright_black",
19
+ "bright_red",
20
+ "bright_green",
21
+ "bright_yellow",
22
+ "bright_blue",
23
+ "bright_magenta",
24
+ "bright_cyan",
25
+ "bright_white",
26
+ "grey0",
27
+ "navy_blue",
28
+ "dark_blue",
29
+ "blue3",
30
+ "blue1",
31
+ "dark_green",
32
+ "deep_sky_blue4",
33
+ "dodger_blue3",
34
+ "dodger_blue2",
35
+ "green4",
36
+ "spring_green4",
37
+ "turquoise4",
38
+ "deep_sky_blue3",
39
+ "dodger_blue1",
40
+ "dark_cyan",
41
+ "light_sea_green",
42
+ "deep_sky_blue2",
43
+ "deep_sky_blue1",
44
+ "green3",
45
+ "spring_green3",
46
+ "cyan3",
47
+ "dark_turquoise",
48
+ "turquoise2",
49
+ "green1",
50
+ "spring_green2",
51
+ "spring_green1",
52
+ "medium_spring_green",
53
+ "cyan2",
54
+ "cyan1",
55
+ "purple4",
56
+ "purple3",
57
+ "blue_violet",
58
+ "grey37",
59
+ "medium_purple4",
60
+ "slate_blue3",
61
+ "royal_blue1",
62
+ "chartreuse4",
63
+ "pale_turquoise4",
64
+ "steel_blue",
65
+ "steel_blue3",
66
+ "cornflower_blue",
67
+ "dark_sea_green4",
68
+ "cadet_blue",
69
+ "sky_blue3",
70
+ "chartreuse3",
71
+ "sea_green3",
72
+ "aquamarine3",
73
+ "medium_turquoise",
74
+ "steel_blue1",
75
+ "sea_green2",
76
+ "sea_green1",
77
+ "dark_slate_gray2",
78
+ "dark_red",
79
+ "dark_magenta",
80
+ "orange4",
81
+ "light_pink4",
82
+ "plum4",
83
+ "medium_purple3",
84
+ "slate_blue1",
85
+ "wheat4",
86
+ "grey53",
87
+ "light_slate_grey",
88
+ "medium_purple",
89
+ "light_slate_blue",
90
+ "yellow4",
91
+ "dark_sea_green",
92
+ "light_sky_blue3",
93
+ "sky_blue2",
94
+ "chartreuse2",
95
+ "pale_green3",
96
+ "dark_slate_gray3",
97
+ "sky_blue1",
98
+ "chartreuse1",
99
+ "light_green",
100
+ "aquamarine1",
101
+ "dark_slate_gray1",
102
+ "deep_pink4",
103
+ "medium_violet_red",
104
+ "dark_violet",
105
+ "purple",
106
+ "medium_orchid3",
107
+ "medium_orchid",
108
+ "dark_goldenrod",
109
+ "rosy_brown",
110
+ "grey63",
111
+ "medium_purple2",
112
+ "medium_purple1",
113
+ "dark_khaki",
114
+ "navajo_white3",
115
+ "grey69",
116
+ "light_steel_blue3",
117
+ "light_steel_blue",
118
+ "dark_olive_green3",
119
+ "dark_sea_green3",
120
+ "light_cyan3",
121
+ "light_sky_blue1",
122
+ "green_yellow",
123
+ "dark_olive_green2",
124
+ "pale_green1",
125
+ "dark_sea_green2",
126
+ "pale_turquoise1",
127
+ "red3",
128
+ "deep_pink3",
129
+ "magenta3",
130
+ "dark_orange3",
131
+ "indian_red",
132
+ "hot_pink3",
133
+ "hot_pink2",
134
+ "orchid",
135
+ "orange3",
136
+ "light_salmon3",
137
+ "light_pink3",
138
+ "pink3",
139
+ "plum3",
140
+ "violet",
141
+ "gold3",
142
+ "light_goldenrod3",
143
+ "tan",
144
+ "misty_rose3",
145
+ "thistle3",
146
+ "plum2",
147
+ "yellow3",
148
+ "khaki3",
149
+ "light_yellow3",
150
+ "grey84",
151
+ "light_steel_blue1",
152
+ "yellow2",
153
+ "dark_olive_green1",
154
+ "dark_sea_green1",
155
+ "honeydew2",
156
+ "light_cyan1",
157
+ "red1",
158
+ "deep_pink2",
159
+ "deep_pink1",
160
+ "magenta2",
161
+ "magenta1",
162
+ "orange_red1",
163
+ "indian_red1",
164
+ "hot_pink",
165
+ "medium_orchid1",
166
+ "dark_orange",
167
+ "salmon1",
168
+ "light_coral",
169
+ "pale_violet_red1",
170
+ "orchid2",
171
+ "orchid1",
172
+ "orange1",
173
+ "sandy_brown",
174
+ "light_salmon1",
175
+ "light_pink1",
176
+ "pink1",
177
+ "plum1",
178
+ "gold1",
179
+ "light_goldenrod2",
180
+ "navajo_white1",
181
+ "misty_rose1",
182
+ "thistle1",
183
+ "yellow1",
184
+ "light_goldenrod1",
185
+ "khaki1",
186
+ "wheat1",
187
+ "cornsilk1",
188
+ "grey100",
189
+ "grey3",
190
+ "grey7",
191
+ "grey11",
192
+ "grey15",
193
+ "grey19",
194
+ "grey23",
195
+ "grey27",
196
+ "grey30",
197
+ "grey35",
198
+ "grey39",
199
+ "grey42",
200
+ "grey46",
201
+ "grey50",
202
+ "grey54",
203
+ "grey58",
204
+ "grey62",
205
+ "grey66",
206
+ "grey70",
207
+ "grey74",
208
+ "grey78",
209
+ "grey82",
210
+ "grey85",
211
+ "grey89",
212
+ "grey93",
213
+ ]
214
+
215
+ basic_colors = [
216
+ "black",
217
+ "red",
218
+ "green",
219
+ "yellow",
220
+ "blue",
221
+ "magenta",
222
+ "cyan",
223
+ "white",
224
+ "bright_black",
225
+ "bright_red",
226
+ "bright_green",
227
+ "bright_yellow",
228
+ "bright_blue",
229
+ "bright_magenta",
230
+ "bright_cyan",
231
+ "bright_white",
232
+ ]
233
+
234
+ def rcolor_all_str():
235
+ pprint(all_colors)
236
+
237
+ def rcolor_basic_str():
238
+ pprint(basic_colors)
239
+
240
+ def rcolor_str(in_str, color="white"):
241
+ assert color in all_colors, f"color must be one of {all_colors}"
242
+ return f"[{color}]{in_str}[/{color}]"
243
+
244
+ def rcolor_palette(color_list):
245
+ # make sure all colors are valid (in all_colors)
246
+ for color in color_list:
247
+ assert (
248
+ color in all_colors
249
+ ), f"color must be a valid color. call <rcolor_all_str()> or <rcolor_palette_all()> to see all valid colors"
250
+ # Initialize console
251
+ console = Console()
252
+
253
+ # Create a table with horizontal lines and six columns
254
+ table = Table(show_header=True, header_style="bold magenta", show_lines=True)
255
+
256
+ # Define the columns
257
+ table.add_column("Color Name 1", style="bold")
258
+ table.add_column("Sample 1", style="bold")
259
+ table.add_column("Color Name 2", style="bold")
260
+ table.add_column("Sample 2", style="bold")
261
+ table.add_column("Color Name 3", style="bold")
262
+ table.add_column("Sample 3", style="bold")
263
+
264
+ # Adjust the number of rows needed for the table
265
+ num_colors = len(color_list)
266
+ num_rows = (num_colors + 2) // 3 # Ceiling division to ensure all colors fit
267
+
268
+ # Add rows to the table
269
+ for i in range(num_rows):
270
+ color1 = color_list[i * 3] if i * 3 < num_colors else ""
271
+ color2 = color_list[i * 3 + 1] if i * 3 + 1 < num_colors else ""
272
+ color3 = color_list[i * 3 + 2] if i * 3 + 2 < num_colors else ""
273
+ filled_rect1 = Text(" " * 10, style=f"on {color1}") if color1 else ""
274
+ filled_rect2 = Text(" " * 10, style=f"on {color2}") if color2 else ""
275
+ filled_rect3 = Text(" " * 10, style=f"on {color3}") if color3 else ""
276
+ table.add_row(color1, filled_rect1, color2, filled_rect2, color3, filled_rect3)
277
+
278
+ # Print the table
279
+ console.print(table)
280
+
281
+ def rcolor_palette_basic():
282
+ rcolor_palette(basic_colors)
283
+
284
+ def rcolor_palette_all():
285
+ rcolor_palette(all_colors)
halib/sys/filesys.py CHANGED
@@ -4,7 +4,7 @@ import shutil
4
4
  from distutils.dir_util import copy_tree
5
5
 
6
6
 
7
- def is_exit(path):
7
+ def is_exist(path):
8
8
  return os.path.exists(path)
9
9
 
10
10
 
@@ -38,8 +38,9 @@ def make_dir(directory):
38
38
 
39
39
 
40
40
  def copy_dir(src_dir, dst_dir, dirs_exist_ok=True, ignore_patterns=None):
41
- shutil.copytree(src_dir, dst_dir, dirs_exist_ok=dirs_exist_ok,
42
- ignore=ignore_patterns)
41
+ shutil.copytree(
42
+ src_dir, dst_dir, dirs_exist_ok=dirs_exist_ok, ignore=ignore_patterns
43
+ )
43
44
 
44
45
 
45
46
  def delete_dir(directory):
@@ -47,14 +48,20 @@ def delete_dir(directory):
47
48
 
48
49
 
49
50
  def list_dirs(directory):
50
- folders = list(filter(lambda x: os.path.isdir(os.path.join(directory, x)),
51
- os.listdir(directory)))
51
+ folders = list(
52
+ filter(
53
+ lambda x: os.path.isdir(os.path.join(directory, x)), os.listdir(directory)
54
+ )
55
+ )
52
56
  return folders
53
57
 
54
58
 
55
59
  def list_files(directory):
56
- files = list(filter(lambda x: os.path.isfile(os.path.join(directory, x)),
57
- os.listdir(directory)))
60
+ files = list(
61
+ filter(
62
+ lambda x: os.path.isfile(os.path.join(directory, x)), os.listdir(directory)
63
+ )
64
+ )
58
65
  return files
59
66
 
60
67
 
@@ -66,12 +73,12 @@ def filter_files_by_extension(directory, ext, recursive=True):
66
73
  else:
67
74
  ext_list = [ext]
68
75
  if not recursive:
69
- filter_pattern = f'{directory}/*'
76
+ filter_pattern = f"{directory}/*"
70
77
  else:
71
- filter_pattern = f'{directory}/**/*'
78
+ filter_pattern = f"{directory}/**/*"
72
79
 
73
80
  for ext_item in ext_list:
74
- ext_filter = f'{filter_pattern}.{ext_item}'
81
+ ext_filter = f"{filter_pattern}.{ext_item}"
75
82
  files = glob.glob(filter_pattern, recursive=True)
76
83
  files = [f for f in files if is_file(f) and f.endswith(ext_item)]
77
84
  result_files.extend(files)
File without changes
halib/system/cmd.py ADDED
@@ -0,0 +1,8 @@
1
+ import subprocess
2
+
3
+
4
+ # @link: https://geekflare.com/learn-python-subprocess/
5
+ # subprocess.run(['ls', '-la'])
6
+ def run(args):
7
+ process = subprocess.run(args, capture_output=True, text=True)
8
+ return process.returncode
@@ -0,0 +1,124 @@
1
+ import glob
2
+ import os
3
+ import shutil
4
+ from distutils.dir_util import copy_tree
5
+
6
+
7
+ def is_exist(path):
8
+ return os.path.exists(path)
9
+
10
+
11
+ def is_directory(path):
12
+ return os.path.isdir(path)
13
+
14
+
15
+ def get_current_dir():
16
+ return os.getcwd()
17
+
18
+
19
+ def change_current_dir(new_dir):
20
+ if is_directory(new_dir):
21
+ os.chdir(new_dir)
22
+
23
+
24
+ def get_dir_name(directory):
25
+ return os.path.basename(os.path.normpath(directory))
26
+
27
+
28
+ def get_parent_dir(directory, return_full_path=False):
29
+ if not return_full_path:
30
+ return os.path.basename(os.path.dirname(directory))
31
+ else:
32
+ return os.path.dirname(directory)
33
+
34
+
35
+ def make_dir(directory):
36
+ if not os.path.exists(directory):
37
+ os.makedirs(directory)
38
+
39
+
40
+ def copy_dir(src_dir, dst_dir, dirs_exist_ok=True, ignore_patterns=None):
41
+ shutil.copytree(
42
+ src_dir, dst_dir, dirs_exist_ok=dirs_exist_ok, ignore=ignore_patterns
43
+ )
44
+
45
+
46
+ def delete_dir(directory):
47
+ shutil.rmtree(directory)
48
+
49
+
50
+ def list_dirs(directory):
51
+ folders = list(
52
+ filter(
53
+ lambda x: os.path.isdir(os.path.join(directory, x)), os.listdir(directory)
54
+ )
55
+ )
56
+ return folders
57
+
58
+
59
+ def list_files(directory):
60
+ files = list(
61
+ filter(
62
+ lambda x: os.path.isfile(os.path.join(directory, x)), os.listdir(directory)
63
+ )
64
+ )
65
+ return files
66
+
67
+
68
+ def filter_files_by_extension(directory, ext, recursive=True):
69
+ if is_directory(directory):
70
+ result_files = []
71
+ if isinstance(ext, list):
72
+ ext_list = ext
73
+ else:
74
+ ext_list = [ext]
75
+ if not recursive:
76
+ filter_pattern = f"{directory}/*"
77
+ else:
78
+ filter_pattern = f"{directory}/**/*"
79
+
80
+ for ext_item in ext_list:
81
+ ext_filter = f"{filter_pattern}.{ext_item}"
82
+ files = glob.glob(filter_pattern, recursive=True)
83
+ files = [f for f in files if is_file(f) and f.endswith(ext_item)]
84
+ result_files.extend(files)
85
+ return result_files
86
+ else:
87
+ raise OSError("Directory not exists")
88
+
89
+
90
+ def is_file(path):
91
+ return os.path.isfile(path)
92
+
93
+
94
+ def get_file_name(file_path, split_file_ext=False):
95
+ if is_file(file_path):
96
+ if split_file_ext:
97
+ filename, file_extension = os.path.splitext(os.path.basename(file_path))
98
+ return filename, file_extension
99
+ else:
100
+ return os.path.basename(file_path)
101
+ else:
102
+ raise OSError("Not a file")
103
+
104
+
105
+ def get_absolute_path(file_path):
106
+ return os.path.abspath(file_path)
107
+
108
+
109
+ # dest can be a directory
110
+ def copy_file(source, dest):
111
+ shutil.copy2(source, dest)
112
+
113
+
114
+ def delete_file(path):
115
+ if is_file(path):
116
+ os.remove(path)
117
+
118
+
119
+ def rename_dir_or_file(old, new):
120
+ os.renames(old, new)
121
+
122
+
123
+ def move_dir_or_file(source, destination):
124
+ shutil.move(source, destination)
halib/tele_noti.py ADDED
@@ -0,0 +1,166 @@
1
+ # Watch a log file and send a telegram message when train reaches a certain epoch or end
2
+
3
+ import os
4
+ import yaml
5
+ import asyncio
6
+ import telegram
7
+ import pandas as pd
8
+
9
+ from rich.pretty import pprint
10
+ from rich.console import Console
11
+ import plotly.graph_objects as go
12
+
13
+ from .system import filesys as fs
14
+ from .filetype import textfile, csvfile
15
+
16
+ from argparse import ArgumentParser
17
+
18
+ tele_console = Console()
19
+
20
+
21
+ def parse_args():
22
+ parser = ArgumentParser(description="desc text")
23
+ parser.add_argument(
24
+ "-cfg",
25
+ "--cfg",
26
+ type=str,
27
+ help="yaml file for tele",
28
+ default=r"E:\Dev\halib\cfg_tele_noti.yaml",
29
+ )
30
+
31
+ return parser.parse_args()
32
+
33
+
34
+ def get_watcher_message_df(target_file, num_last_lines):
35
+ file_ext = fs.get_file_name(target_file, split_file_ext=True)[1]
36
+ supported_ext = [".txt", ".log", ".csv"]
37
+ assert (
38
+ file_ext in supported_ext
39
+ ), f"File extension {file_ext} not supported. Supported extensions are {supported_ext}"
40
+ last_lines_df = None
41
+ if file_ext in [".txt", ".log"]:
42
+ lines = textfile.read_line_by_line(target_file)
43
+ if num_last_lines > len(lines):
44
+ num_last_lines = len(lines)
45
+ last_line_arr = lines[-num_last_lines:]
46
+ # add a line start with word "epoch"
47
+ epoch_info_list = "Epoch: n/a"
48
+ for line in reversed(lines):
49
+ if "epoch" in line.lower():
50
+ epoch_info_list = line
51
+ break
52
+ last_line_arr.insert(0, epoch_info_list) # insert at the beginning
53
+ dfCreator = csvfile.DFCreator()
54
+ dfCreator.create_table("last_lines", ["line"])
55
+ last_line_arr = [[line] for line in last_line_arr]
56
+ dfCreator.insert_rows("last_lines", last_line_arr)
57
+ dfCreator.fill_table_from_row_pool("last_lines")
58
+ last_lines_df = dfCreator["last_lines"].copy()
59
+ else:
60
+ df = pd.read_csv(target_file)
61
+ num_rows = len(df)
62
+ if num_last_lines > num_rows:
63
+ num_last_lines = num_rows
64
+ last_lines_df = df.tail(num_last_lines)
65
+ return last_lines_df
66
+
67
+
68
+ def df2img(df: pd.DataFrame, output_img_dir, decimal_places, out_img_scale):
69
+ df = df.round(decimal_places)
70
+ fig = go.Figure(
71
+ data=[
72
+ go.Table(
73
+ header=dict(values=list(df.columns), align="center"),
74
+ cells=dict(
75
+ values=df.values.transpose(),
76
+ fill_color=[["white", "lightgrey"] * df.shape[0]],
77
+ align="center",
78
+ ),
79
+ )
80
+ ]
81
+ )
82
+ if not os.path.exists(output_img_dir):
83
+ os.makedirs(output_img_dir)
84
+ img_path = os.path.normpath(os.path.join(output_img_dir, "last_lines.png"))
85
+ fig.write_image(img_path, scale=out_img_scale)
86
+ return img_path
87
+
88
+
89
+ def compose_message_and_img_path(
90
+ target_file, project, num_last_lines, decimal_places, out_img_scale, output_img_dir
91
+ ):
92
+ context_msg = f">> Project: {project} \n>> File: {target_file} \n>> Last {num_last_lines} lines:"
93
+ msg_df = get_watcher_message_df(target_file, num_last_lines)
94
+ try:
95
+ img_path = df2img(msg_df, output_img_dir, decimal_places, out_img_scale)
96
+ except Exception as e:
97
+ pprint(f"Error: {e}")
98
+ img_path = None
99
+ return context_msg, img_path
100
+
101
+
102
+ async def send_to_telegram(cfg_dict, interval_in_sec):
103
+ # pprint(cfg_dict)
104
+ token = cfg_dict["telegram"]["token"]
105
+ chat_id = cfg_dict["telegram"]["chat_id"]
106
+
107
+ noti_settings = cfg_dict["noti_settings"]
108
+ project = noti_settings["project"]
109
+ target_file = noti_settings["target_file"]
110
+ num_last_lines = noti_settings["num_last_lines"]
111
+ output_img_dir = noti_settings["output_img_dir"]
112
+ decimal_places = noti_settings["decimal_places"]
113
+ out_img_scale = noti_settings["out_img_scale"]
114
+
115
+ bot = telegram.Bot(token=token)
116
+ async with bot:
117
+ try:
118
+ context_msg, img_path = compose_message_and_img_path(
119
+ target_file,
120
+ project,
121
+ num_last_lines,
122
+ decimal_places,
123
+ out_img_scale,
124
+ output_img_dir,
125
+ )
126
+ time_now = next_time = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")
127
+ sep_line = "-" * 50
128
+ context_msg = f"{sep_line}\n>> Time: {time_now}\n{context_msg}"
129
+ # calculate the next time to send message
130
+ next_time = pd.Timestamp.now() + pd.Timedelta(seconds=interval_in_sec)
131
+ next_time = next_time.strftime("%Y-%m-%d %H:%M:%S")
132
+ next_time_info = f"Next msg: {next_time}"
133
+ tele_console.rule()
134
+ tele_console.print("[green] Send message to telegram [/green]")
135
+ tele_console.print(
136
+ f"[red] Next message will be sent at <{next_time}> [/red]"
137
+ )
138
+ await bot.send_message(text=context_msg, chat_id=chat_id)
139
+ if img_path:
140
+ await bot.send_photo(chat_id=chat_id, photo=open(img_path, "rb"))
141
+ await bot.send_message(text=next_time_info, chat_id=chat_id)
142
+ except Exception as e:
143
+ pprint(f"Error: {e}")
144
+ pprint("Message not sent to telegram")
145
+
146
+
147
+ async def run_forever(cfg_path):
148
+ cfg_dict = yaml.safe_load(open(cfg_path, "r"))
149
+ noti_settings = cfg_dict["noti_settings"]
150
+ interval_in_min = noti_settings["interval_in_min"]
151
+ interval_in_sec = int(interval_in_min * 60)
152
+ pprint(
153
+ f"Message will be sent every {interval_in_min} minutes or {interval_in_sec} seconds"
154
+ )
155
+ while True:
156
+ await send_to_telegram(cfg_dict, interval_in_sec)
157
+ await asyncio.sleep(interval_in_sec)
158
+
159
+
160
+ async def main():
161
+ args = parse_args()
162
+ await run_forever(args.cfg)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ asyncio.run(main())