expr_tracker 0.1.0__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.
@@ -0,0 +1,9 @@
1
+ from . import tracker
2
+ from .alert import alert
3
+
4
+ init = tracker.init
5
+ finish = tracker.finish
6
+ log = tracker.log
7
+ info = tracker.info
8
+
9
+ __all__ = ["init", "finish", "log", "info", "alert"]
expr_tracker/alert.py ADDED
@@ -0,0 +1,55 @@
1
+ from typing import Literal
2
+
3
+
4
+ class LarkBackend:
5
+ def __init__(self, **kwargs):
6
+ from slark import Lark
7
+
8
+ self.lark = Lark(**kwargs)
9
+
10
+ def publish_alert(
11
+ self,
12
+ title: str,
13
+ text: str,
14
+ subtitle: str | None = None,
15
+ level: Literal["info", "warning", "error"] = "info",
16
+ traceback: str | None = None,
17
+ ):
18
+ if level == "info" or level == "warning":
19
+ self.lark.webhook.post_success_card(
20
+ msg=text, title=title, subtitle=subtitle
21
+ )
22
+ elif level == "error":
23
+ self.lark.webhook.post_error_card(
24
+ msg=text, traceback=traceback or "", title=title, subtitle=subtitle
25
+ )
26
+ else:
27
+ raise ValueError(
28
+ f"Unsupported level: {level}. Supported levels are 'info', 'warning', and 'error'."
29
+ )
30
+
31
+
32
+ def alert(
33
+ title: str,
34
+ text: str,
35
+ subtitle: str | None = None,
36
+ traceback: str | None = None,
37
+ level: Literal["info", "warning", "error"] = "info",
38
+ backends: list[Literal["lark", "slack", "email"]] = ["lark"],
39
+ ):
40
+ """
41
+ Send an alert message to specified backends.
42
+
43
+ Args:
44
+ title (str): The title of the alert.
45
+ text (str): The main content of the alert.
46
+ subtitle (str | None): Optional subtitle for the alert.
47
+ level (Literal["info", "warning", "error"]): The severity level of the alert.
48
+ backends (list[Literal["lark", "slack", "email"]]): List of backends to send the alert to.
49
+ """
50
+ for backend in backends:
51
+ if backend == "lark":
52
+ lark_backend = LarkBackend()
53
+ lark_backend.publish_alert(title, text, subtitle, level, traceback)
54
+ else:
55
+ raise NotImplementedError(f"Backend '{backend}' is not implemented.")
expr_tracker/jsonl.py ADDED
@@ -0,0 +1,64 @@
1
+ from loguru import logger
2
+ from pathlib import Path
3
+
4
+
5
+ def custom_json_encoder(obj):
6
+ if isinstance(obj, Path):
7
+ return str(obj)
8
+ raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
9
+
10
+
11
+ class JsonlTracker:
12
+ def __init__(self):
13
+ pass
14
+
15
+ def init(
16
+ self,
17
+ project: str,
18
+ name: str | None = None,
19
+ config: dict | None = None,
20
+ dir: str | None = None,
21
+ print_to_screen: bool = False,
22
+ print_handle=print,
23
+ **kwargs,
24
+ ):
25
+ self.project = project
26
+ self.name = name
27
+ if dir is None:
28
+ dir = "./tracker/jsonl"
29
+ self.log_dir = Path(dir) / self.project / self.name
30
+ self.config_fp = self.log_dir / "config.json"
31
+ self.log_fp = self.log_dir / "metrics.jsonl"
32
+ self.log_dir.mkdir(parents=True, exist_ok=True)
33
+ if self.config_fp.exists():
34
+ logger.warning(
35
+ f"Config file {self.config_fp} already exists. It will be overwritten."
36
+ )
37
+ if config is not None:
38
+ with open(self.config_fp, "w") as f:
39
+ import json
40
+
41
+ json.dump(config, f, indent=4, default=custom_json_encoder)
42
+ self.print_to_screen = print_to_screen
43
+ self.print_handle = print_handle
44
+ self.current_step = (
45
+ 0 if not self.log_fp.exists() else len(self.log_fp.read_text().splitlines())
46
+ )
47
+
48
+ def log(self, metrics: dict, step: int | None = None):
49
+ import jsonlines
50
+
51
+ if not self.log_fp.parent.exists():
52
+ self.log_fp.parent.mkdir(parents=True, exist_ok=True)
53
+
54
+ if step is not None:
55
+ self.current_step = step
56
+ metrics = {"_step": self.current_step, **metrics}
57
+ with jsonlines.open(self.log_fp, mode="a") as writer:
58
+ writer.write(metrics)
59
+ self.current_step += 1
60
+ if self.print_to_screen:
61
+ self.print_handle(f"{metrics}")
62
+
63
+ def finish(self):
64
+ pass
@@ -0,0 +1,155 @@
1
+ from typing import Literal
2
+ from contextvars import ContextVar
3
+ from loguru import logger
4
+
5
+ _tracker: ContextVar["Tracker | None"] = ContextVar("tracker", default=None)
6
+
7
+
8
+ def get_backend(backend: str | object):
9
+ if isinstance(backend, str):
10
+ backend = backend.lower()
11
+ if backend == "wandb":
12
+ try:
13
+ import wandb
14
+
15
+ return wandb
16
+ except ImportError:
17
+ raise ImportError("WandB backend is not installed.")
18
+ elif backend == "jsonl":
19
+ from .jsonl import JsonlTracker
20
+
21
+ return JsonlTracker()
22
+ elif backend == "trackio":
23
+ try:
24
+ import trackio
25
+
26
+ return trackio
27
+ except ImportError:
28
+ raise ImportError("TrackIO backend is not installed.")
29
+ else:
30
+ return backend
31
+
32
+
33
+ class Tracker:
34
+ def __init__(
35
+ self,
36
+ project: str,
37
+ name: str | None = None,
38
+ entity: str | None = None,
39
+ dir: str | None = None,
40
+ notes: str | None = None,
41
+ tags: list[str] | None = None,
42
+ resume: bool | Literal["allow", "never", "must", "auto"] | None = "allow",
43
+ config: dict | None = None,
44
+ backends: list[Literal["wandb", "jsonl", "trackio"]] = [
45
+ "wandb",
46
+ "trackio",
47
+ "jsonl",
48
+ ],
49
+ backend_kwargs: dict[str, dict] | None = None,
50
+ **kwargs,
51
+ ):
52
+ if kwargs:
53
+ logger.info(
54
+ f"Unrecognized keyword arguments passed to Tracker: {kwargs}. "
55
+ "These will be ignored."
56
+ )
57
+ self.backend = {b: get_backend(b) for b in backends}
58
+ if config is None:
59
+ config = {}
60
+ if backend_kwargs is None:
61
+ backend_kwargs = {}
62
+ for b, t in self.backend.items():
63
+ if b == "trackio":
64
+ t.init(
65
+ project=project,
66
+ name=name,
67
+ config=config.update(
68
+ {
69
+ "trackio.notes": notes,
70
+ "trackio.tags": tags,
71
+ "trackio.entity": entity,
72
+ "trackio.resume": resume,
73
+ }
74
+ ),
75
+ **backend_kwargs.get(b, {}),
76
+ )
77
+ else:
78
+ t.init(
79
+ project=project,
80
+ name=name,
81
+ entity=entity,
82
+ dir=dir,
83
+ notes=notes,
84
+ tags=tags,
85
+ resume=resume,
86
+ id=name,
87
+ config=config,
88
+ **backend_kwargs.get(b, {}),
89
+ )
90
+
91
+
92
+ def init(
93
+ project: str,
94
+ name: str | None = None,
95
+ entity: str | None = None,
96
+ dir: str | None = None,
97
+ notes: str | None = None,
98
+ tags: list[str] | None = None,
99
+ resume: bool | Literal["allow", "never", "must", "auto"] | None = "allow",
100
+ config: dict | None = None,
101
+ backends: list[Literal["wandb", "jsonl", "trackio"]] = ["wandb", "jsonl"],
102
+ backend_kwargs: dict[str, dict] | None = None,
103
+ **kwargs,
104
+ ) -> Tracker:
105
+ """
106
+ Initialize the tracker with the given parameters.
107
+ """
108
+ if _tracker.get() is not None:
109
+ raise RuntimeError("Tracker is already initialized. Call finish() first.")
110
+
111
+ tracker = Tracker(
112
+ project=project,
113
+ name=name,
114
+ entity=entity,
115
+ dir=dir,
116
+ notes=notes,
117
+ tags=tags,
118
+ resume=resume,
119
+ config=config,
120
+ backends=backends,
121
+ backend_kwargs=backend_kwargs,
122
+ **kwargs,
123
+ )
124
+ _tracker.set(tracker)
125
+ return tracker
126
+
127
+
128
+ def log(metrics: dict, step: int | None = None):
129
+ tracker = _tracker.get()
130
+ if tracker is None:
131
+ raise RuntimeError("Tracker is not initialized. Call init() first.")
132
+ for b in tracker.backend.values():
133
+ b.log(metrics, step=step)
134
+
135
+
136
+ def finish():
137
+ tracker = _tracker.get()
138
+ if tracker is None:
139
+ raise RuntimeError("Tracker is not initialized. Call init() first.")
140
+ for b in tracker.backend.values():
141
+ b.finish()
142
+ _tracker.set(None)
143
+
144
+
145
+ def info():
146
+ tracker = _tracker.get()
147
+ if tracker is None:
148
+ raise RuntimeError("Tracker is not initialized. Call init() first.")
149
+ info = {}
150
+ for b, t in tracker.backend.items():
151
+ if b == "wandb":
152
+ info["wandb"] = {"url": t.run.url}
153
+ if b == "jsonl":
154
+ info["jsonl"] = {"log_dir": t.log_dir.as_posix()}
155
+ return info
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: expr_tracker
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author-email: HSPK <whxway@whu.edu.cn>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: jsonlines>=4.0.0
8
+ Requires-Dist: loguru>=0.7.3
9
+ Requires-Dist: slark>=0.1.27
10
+ Requires-Dist: wandb>=0.21.0
@@ -0,0 +1,7 @@
1
+ expr_tracker/__init__.py,sha256=I-Y4K79ZSh0thhpQehfFVG2CvWpXKoQSwNFn05UN4uY,184
2
+ expr_tracker/alert.py,sha256=9jg7eXmeta6DZHR8ets7ao0udV9xvEcrIs5Grb3_ugY,1807
3
+ expr_tracker/jsonl.py,sha256=Ni0qZYgcX_AlRs6qpPAtjrw_SFqwQZ_yjxl1agSOZAs,1997
4
+ expr_tracker/tracker.py,sha256=7OWmG8tDairTHEHMFTPnq3hElQWK6T0w_BfU-KkqKRM,4575
5
+ expr_tracker-0.1.0.dist-info/METADATA,sha256=c4y7TQz6HRRh6AbZ0edaImyXGjglWU8XsyqckjmQMj8,273
6
+ expr_tracker-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ expr_tracker-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any