halib 0.2.13__py3-none-any.whl → 0.2.15__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.
@@ -1,17 +1,62 @@
1
1
  import os
2
2
  import time
3
3
  import json
4
-
5
4
  from pathlib import Path
6
5
  from pprint import pprint
7
6
  from threading import Lock
7
+ from functools import wraps
8
8
  from loguru import logger
9
9
 
10
+ # Plotting libraries
10
11
  from plotly.subplots import make_subplots
11
12
  import plotly.graph_objects as go
12
13
  import plotly.express as px # for dynamic color scales
13
14
 
15
+ from contextlib import contextmanager
16
+
14
17
  from ...common.common import ConsoleLog
18
+ from ...system.path import *
19
+
20
+
21
+ # ==========================================
22
+ # 1. The Decorator
23
+ # ==========================================
24
+ def check_enabled(func):
25
+ """
26
+ Decorator to skip method execution if the profiler is disabled.
27
+
28
+ This acts as a 'guard clause' for the entire function. If the profiler
29
+ instance has 'enabled=False', the decorated function is not executed at all,
30
+ saving processing time and avoiding side effects.
31
+ """
32
+
33
+ @wraps(func)
34
+ def wrapper(self, *args, **kwargs):
35
+ # Gracefully handle cases where 'enabled' might not be set yet (default to True)
36
+ # print('check_enabled called')
37
+ if not getattr(self, "enabled", True):
38
+ # print('Profiler disabled, skipping function execution.')
39
+ return # Exit immediately, returning None
40
+ return func(self, *args, **kwargs)
41
+
42
+ return wrapper
43
+
44
+
45
+ # ==========================================
46
+ # 2. The Class
47
+ # ==========================================
48
+ class ContextScope:
49
+ """Helper to remember the current context name so you don't have to repeat it."""
50
+
51
+ def __init__(self, profiler, ctx_name):
52
+ self.profiler = profiler
53
+ self.ctx_name = ctx_name
54
+
55
+ @contextmanager
56
+ def step(self, step_name):
57
+ # Automatically passes the stored ctx_name + the new step_name
58
+ with self.profiler.measure(self.ctx_name, step_name):
59
+ yield
15
60
 
16
61
 
17
62
  class zProfiler:
@@ -23,13 +68,23 @@ class zProfiler:
23
68
  output_file (str): Path to save the profiling report.
24
69
  report_format (str): Output format for reports ("json" or "csv").
25
70
 
26
- Example:
71
+ Example (using context manager):
72
+ prof = zProfiler()
73
+ with prof.measure("my_context") as ctx:
74
+ with ctx.step("step1"):
75
+ time.sleep(0.1)
76
+ with ctx.step("step2"):
77
+ time.sleep(0.2)
78
+
79
+ Example (using raw methods):
27
80
  prof = zProfiler()
28
81
  prof.ctx_start("my_context")
29
82
  prof.step_start("my_context", "step1")
30
83
  time.sleep(0.1)
31
84
  prof.step_end("my_context", "step1")
32
85
  prof.ctx_end("my_context")
86
+
87
+
33
88
  """
34
89
 
35
90
  _instance = None
@@ -41,13 +96,24 @@ class zProfiler:
41
96
  cls._instance = super().__new__(cls)
42
97
  return cls._instance
43
98
 
44
- def __init__(
45
- self,
46
- ):
99
+ def __init__(self, enabled=None):
100
+ """
101
+ Args:
102
+ enabled (bool, optional):
103
+ - If True/False: Updates the enabled state immediately.
104
+ - If None: Keeps the current state (defaults to True on first init).
105
+ """
106
+ # 1. First-time initialization
47
107
  if not hasattr(self, "_initialized"):
108
+ self.enabled = enabled if enabled is not None else True
48
109
  self.time_dict = {}
49
110
  self._initialized = True
50
111
 
112
+ # 2. If initialized, allow updating 'enabled' ONLY if explicitly passed
113
+ elif enabled is not None:
114
+ self.enabled = enabled
115
+
116
+ @check_enabled
51
117
  def ctx_start(self, ctx_name="ctx_default"):
52
118
  if not isinstance(ctx_name, str) or not ctx_name:
53
119
  raise ValueError("ctx_name must be a non-empty string")
@@ -59,6 +125,7 @@ class zProfiler:
59
125
  }
60
126
  self.time_dict[ctx_name]["report_count"] += 1
61
127
 
128
+ @check_enabled
62
129
  def ctx_end(self, ctx_name="ctx_default", report_func=None):
63
130
  if ctx_name not in self.time_dict:
64
131
  return
@@ -67,6 +134,7 @@ class zProfiler:
67
134
  self.time_dict[ctx_name]["end"] - self.time_dict[ctx_name]["start"]
68
135
  )
69
136
 
137
+ @check_enabled
70
138
  def step_start(self, ctx_name, step_name):
71
139
  if not isinstance(step_name, str) or not step_name:
72
140
  raise ValueError("step_name must be a non-empty string")
@@ -76,6 +144,7 @@ class zProfiler:
76
144
  self.time_dict[ctx_name]["step_dict"][step_name] = []
77
145
  self.time_dict[ctx_name]["step_dict"][step_name].append([time.perf_counter()])
78
146
 
147
+ @check_enabled
79
148
  def step_end(self, ctx_name, step_name):
80
149
  if (
81
150
  ctx_name not in self.time_dict
@@ -84,6 +153,24 @@ class zProfiler:
84
153
  return
85
154
  self.time_dict[ctx_name]["step_dict"][step_name][-1].append(time.perf_counter())
86
155
 
156
+ @contextmanager
157
+ def measure(self, ctx_name, step_name=None):
158
+ if step_name is None:
159
+ # --- Context Mode ---
160
+ self.ctx_start(ctx_name)
161
+ try:
162
+ # Yield the helper object initialized with the current context name
163
+ yield ContextScope(self, ctx_name)
164
+ finally:
165
+ self.ctx_end(ctx_name)
166
+ else:
167
+ # --- Step Mode ---
168
+ self.step_start(ctx_name, step_name)
169
+ try:
170
+ yield
171
+ finally:
172
+ self.step_end(ctx_name, step_name)
173
+
87
174
  def _step_dict_to_detail(self, ctx_step_dict):
88
175
  """
89
176
  'ctx_step_dict': {
@@ -167,7 +254,6 @@ class zProfiler:
167
254
  )
168
255
  return report_dict
169
256
 
170
- @classmethod
171
257
  @classmethod
172
258
  def plot_formatted_data(
173
259
  cls, profiler_data, outdir=None, file_format="png", do_show=False, tag=""
@@ -261,7 +347,8 @@ class zProfiler:
261
347
  file_prefix = ctx if len(tag_str) == 0 else f"{tag_str}_{ctx}"
262
348
  file_path = os.path.join(outdir, f"{file_prefix}_summary.{file_format.lower()}")
263
349
  fig.write_image(file_path)
264
- print(f"Saved figure: {file_path}")
350
+ pprint(f"Saved figure to: 🔽")
351
+ pprint_local_path(file_path)
265
352
 
266
353
  results[ctx] = fig
267
354
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halib
3
- Version: 0.2.13
3
+ Version: 0.2.15
4
4
  Summary: Small library for common tasks
5
5
  Author: Hoang Van Ha
6
6
  Author-email: hoangvanhauit@gmail.com
@@ -41,6 +41,7 @@ Requires-Dist: tqdm
41
41
  Requires-Dist: tube_dl
42
42
  Requires-Dist: wandb
43
43
  Requires-Dist: ipynbname
44
+ Requires-Dist: typed-argument-parser
44
45
  Dynamic: author
45
46
  Dynamic: author-email
46
47
  Dynamic: classifier
@@ -53,6 +54,9 @@ Dynamic: summary
53
54
 
54
55
  # Helper package for coding and automation
55
56
 
57
+ **Version 0.2.15**
58
+ + update `exp.perf.profiler`: add `enabled` flag to enable/disable profiling dynamically, also add `measure` context manager to simplify measuring code blocks.
59
+
56
60
  **Version 0.2.13**
57
61
  + reorganize packages with most changes in `research` package; also rename `research` to `exp` (package for experiment management and utilities)
58
62
  + update `exp/perfcalc.py` to allow save computed performance to csv file (without explicit calling method `calc_perfs`)
@@ -35,7 +35,7 @@ halib/exp/perf/gpu_mon.py,sha256=vD41_ZnmPLKguuq9X44SB_vwd9JrblO4BDzHLXZhhFY,223
35
35
  halib/exp/perf/perfcalc.py,sha256=p7rhVShiie7DT_s50lbvbGftVCkrWE0tQGFLUEmTXi0,18326
36
36
  halib/exp/perf/perfmetrics.py,sha256=qRiNiCKGUSTLY7gPMVMuVHGAAyeosfGWup2eM4490aw,5485
37
37
  halib/exp/perf/perftb.py,sha256=IWElg3OB5dmhfxnY8pMZvkL2y_EnvLmEx3gJlpUR1Fs,31066
38
- halib/exp/perf/profiler.py,sha256=5ZjES8kAqEsSV1mC3Yr_1ivFLwQDc_yv4HY7dKt_AS0,11782
38
+ halib/exp/perf/profiler.py,sha256=jWcOu4tujac-8eoRMmqLtOv71qIx-FAi9Ri_35Jj0W8,14817
39
39
  halib/exp/viz/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  halib/exp/viz/plot.py,sha256=51FhD1mH4nthTrY3K4mrO5pxI5AzvHXpZy5_ToqkYHs,28580
41
41
  halib/filetype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +102,8 @@ halib/utils/list.py,sha256=BM-8sRhYyqF7bh4p7TQtV7P_gnFruUCA6DTUOombaZg,337
102
102
  halib/utils/listop.py,sha256=Vpa8_2fI0wySpB2-8sfTBkyi_A4FhoFVVvFiuvW8N64,339
103
103
  halib/utils/tele_noti.py,sha256=-4WXZelCA4W9BroapkRyIdUu9cUVrcJJhegnMs_WpGU,5928
104
104
  halib/utils/video.py,sha256=zLoj5EHk4SmP9OnoHjO8mLbzPdtq6gQPzTQisOEDdO8,3261
105
- halib-0.2.13.dist-info/licenses/LICENSE.txt,sha256=qZssdna4aETiR8znYsShUjidu-U4jUT9Q-EWNlZ9yBQ,1100
106
- halib-0.2.13.dist-info/METADATA,sha256=f0Ammv1hiSO74ejAz5V_u6o73J8EzeFyoPBZ2Z3S_Ig,6838
107
- halib-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
108
- halib-0.2.13.dist-info/top_level.txt,sha256=7AD6PLaQTreE0Fn44mdZsoHBe_Zdd7GUmjsWPyQ7I-k,6
109
- halib-0.2.13.dist-info/RECORD,,
105
+ halib-0.2.15.dist-info/licenses/LICENSE.txt,sha256=qZssdna4aETiR8znYsShUjidu-U4jUT9Q-EWNlZ9yBQ,1100
106
+ halib-0.2.15.dist-info/METADATA,sha256=ZYq53Ob6tAw34B5DOE1jTRL5rafRtXK6ie7xtn7JSSA,7059
107
+ halib-0.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
108
+ halib-0.2.15.dist-info/top_level.txt,sha256=7AD6PLaQTreE0Fn44mdZsoHBe_Zdd7GUmjsWPyQ7I-k,6
109
+ halib-0.2.15.dist-info/RECORD,,
File without changes