saba9 HF Staff commited on
Commit
02610bc
·
verified ·
1 Parent(s): 9b2cbf8

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. trackio/CHANGELOG.md +152 -0
  3. trackio/__init__.py +601 -0
  4. trackio/__pycache__/__init__.cpython-312.pyc +0 -0
  5. trackio/__pycache__/__init__.cpython-313.pyc +0 -0
  6. trackio/__pycache__/__init__.cpython-314.pyc +0 -0
  7. trackio/__pycache__/api.cpython-312.pyc +0 -0
  8. trackio/__pycache__/cli.cpython-312.pyc +0 -0
  9. trackio/__pycache__/cli_helpers.cpython-312.pyc +0 -0
  10. trackio/__pycache__/commit_scheduler.cpython-312.pyc +0 -0
  11. trackio/__pycache__/commit_scheduler.cpython-313.pyc +0 -0
  12. trackio/__pycache__/context_vars.cpython-312.pyc +0 -0
  13. trackio/__pycache__/context_vars.cpython-313.pyc +0 -0
  14. trackio/__pycache__/deploy.cpython-312.pyc +0 -0
  15. trackio/__pycache__/deploy.cpython-313.pyc +0 -0
  16. trackio/__pycache__/dummy_commit_scheduler.cpython-312.pyc +0 -0
  17. trackio/__pycache__/dummy_commit_scheduler.cpython-313.pyc +0 -0
  18. trackio/__pycache__/file_storage.cpython-312.pyc +0 -0
  19. trackio/__pycache__/gpu.cpython-312.pyc +0 -0
  20. trackio/__pycache__/histogram.cpython-312.pyc +0 -0
  21. trackio/__pycache__/imports.cpython-312.pyc +0 -0
  22. trackio/__pycache__/imports.cpython-313.pyc +0 -0
  23. trackio/__pycache__/media.cpython-312.pyc +0 -0
  24. trackio/__pycache__/media_commit_scheduler.cpython-312.pyc +0 -0
  25. trackio/__pycache__/run.cpython-312.pyc +0 -0
  26. trackio/__pycache__/run.cpython-313.pyc +0 -0
  27. trackio/__pycache__/sqlite_storage.cpython-312.pyc +0 -0
  28. trackio/__pycache__/sqlite_storage.cpython-313.pyc +0 -0
  29. trackio/__pycache__/sqlite_types.cpython-312.pyc +0 -0
  30. trackio/__pycache__/table.cpython-312.pyc +0 -0
  31. trackio/__pycache__/typehints.cpython-312.pyc +0 -0
  32. trackio/__pycache__/ui.cpython-312.pyc +0 -0
  33. trackio/__pycache__/ui.cpython-313.pyc +0 -0
  34. trackio/__pycache__/utils.cpython-312.pyc +0 -0
  35. trackio/__pycache__/utils.cpython-313.pyc +0 -0
  36. trackio/__pycache__/video_writer.cpython-312.pyc +0 -0
  37. trackio/api.py +66 -0
  38. trackio/assets/badge.png +0 -0
  39. trackio/assets/trackio_logo_dark.png +0 -0
  40. trackio/assets/trackio_logo_light.png +0 -0
  41. trackio/assets/trackio_logo_old.png +3 -0
  42. trackio/assets/trackio_logo_type_dark.png +0 -0
  43. trackio/assets/trackio_logo_type_dark_transparent.png +0 -0
  44. trackio/assets/trackio_logo_type_light.png +0 -0
  45. trackio/assets/trackio_logo_type_light_transparent.png +0 -0
  46. trackio/cli.py +514 -0
  47. trackio/cli_helpers.py +118 -0
  48. trackio/commit_scheduler.py +310 -0
  49. trackio/context_vars.py +18 -0
  50. trackio/deploy.py +433 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ trackio/assets/trackio_logo_old.png filter=lfs diff=lfs merge=lfs -text
trackio/CHANGELOG.md ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # trackio
2
+
3
+ ## 0.16.1
4
+
5
+ ### Features
6
+
7
+ - [#431](https://github.com/gradio-app/trackio/pull/431) [`c7ce55b`](https://github.com/gradio-app/trackio/commit/c7ce55b14dd5eb0c2165fb15df17dd60721c9325) - Lazy load the UI when trackio is imported. Thanks @abidlabs!
8
+
9
+ ## 0.16.0
10
+
11
+ ### Features
12
+
13
+ - [#426](https://github.com/gradio-app/trackio/pull/426) [`ead4dc8`](https://github.com/gradio-app/trackio/commit/ead4dc8e74ee2d8e47d61bca0a7668456acf49be) - Fix redundant double rendering of group checkboxes. Thanks @abidlabs!
14
+ - [#413](https://github.com/gradio-app/trackio/pull/413) [`39c4750`](https://github.com/gradio-app/trackio/commit/39c4750951d554ba6eb4d58847c6bb444b2891a8) - Check `dist-packages` when checking for source installation. Thanks @sergiopaniego!
15
+ - [#423](https://github.com/gradio-app/trackio/pull/423) [`2e52ab3`](https://github.com/gradio-app/trackio/commit/2e52ab303e3041718a6a56fbf84d0848aca9ad67) - Fix legend outline visibility issue. Thanks @Raghunath-Balaji!
16
+ - [#407](https://github.com/gradio-app/trackio/pull/407) [`c8a384d`](https://github.com/gradio-app/trackio/commit/c8a384ddfe5a295cecf862a26178d40e48acb424) - Fix pytests that were failling locally on MacOS. Thanks @abidlabs!
17
+ - [#405](https://github.com/gradio-app/trackio/pull/405) [`35aae4e`](https://github.com/gradio-app/trackio/commit/35aae4e3aa3e2b2888887528478b9dc6a9808bda) - Add conditional padding for HF Space dashboard when not in iframe. Thanks @znation!
18
+
19
+ ## 0.15.0
20
+
21
+ ### Features
22
+
23
+ - [#397](https://github.com/gradio-app/trackio/pull/397) [`6b38ad0`](https://github.com/gradio-app/trackio/commit/6b38ad02e5d73a0df49c4eede7e91331282ece04) - Adds `--host` cli option support. Thanks @abidlabs!
24
+ - [#396](https://github.com/gradio-app/trackio/pull/396) [`4a4d1ab`](https://github.com/gradio-app/trackio/commit/4a4d1ab85e63d923132a3fa7afa5d90e16431bec) - Fix run selection issue. Thanks @abidlabs!
25
+ - [#394](https://github.com/gradio-app/trackio/pull/394) [`c47a3a3`](https://github.com/gradio-app/trackio/commit/c47a3a31f8c4b83bce1aa7fc22eeba3d9021ad3d) - Add wandb-compatible API for trackio. Thanks @abidlabs!
26
+ - [#378](https://github.com/gradio-app/trackio/pull/378) [`b02046a`](https://github.com/gradio-app/trackio/commit/b02046a5b0dad7c9854e099a87f884afba4aecb2) - Add JSON export button for line plots and upgrade gradio dependency. Thanks @JamshedAli18!
27
+
28
+ ## 0.14.2
29
+
30
+ ### Features
31
+
32
+ - [#386](https://github.com/gradio-app/trackio/pull/386) [`f9452cd`](https://github.com/gradio-app/trackio/commit/f9452cdb8f0819368f3610f7ac0ed08957305275) - Fixing some issues related to deployed Trackio Spaces. Thanks @abidlabs!
33
+
34
+ ## 0.14.1
35
+
36
+ ### Features
37
+
38
+ - [#382](https://github.com/gradio-app/trackio/pull/382) [`44fe9bb`](https://github.com/gradio-app/trackio/commit/44fe9bb264fb2aafb0ec302ff15227c045819a2c) - Fix app file path when Trackio is not installed from source. Thanks @abidlabs!
39
+ - [#380](https://github.com/gradio-app/trackio/pull/380) [`c3f4cff`](https://github.com/gradio-app/trackio/commit/c3f4cff74bc5676e812773d8571454894fcdc7cc) - Add CLI commands for querying projects, runs, and metrics. Thanks @abidlabs!
40
+
41
+ ## 0.14.0
42
+
43
+ ### Features
44
+
45
+ - [#377](https://github.com/gradio-app/trackio/pull/377) [`5c5015b`](https://github.com/gradio-app/trackio/commit/5c5015b68c85c5de51111dad983f735c27b9a05f) - fixed wrapping issue in Runs table. Thanks @gaganchapa!
46
+ - [#374](https://github.com/gradio-app/trackio/pull/374) [`388e26b`](https://github.com/gradio-app/trackio/commit/388e26b9e9f24cd7ad203affe9b709be885b3d24) - Save Optimized Parquet files. Thanks @lhoestq!
47
+ - [#371](https://github.com/gradio-app/trackio/pull/371) [`fbace9c`](https://github.com/gradio-app/trackio/commit/fbace9cd7732c166f34d268f54b05bb06846cc5d) - Add GPU metrics logging. Thanks @kashif!
48
+ - [#367](https://github.com/gradio-app/trackio/pull/367) [`862840c`](https://github.com/gradio-app/trackio/commit/862840c13e30fc960cbee5b9eac4d3c25beba9de) - Add option to only show latest run, and fix the double logo issue. Thanks @abidlabs!
49
+
50
+ ## 0.13.1
51
+
52
+ ### Features
53
+
54
+ - [#369](https://github.com/gradio-app/trackio/pull/369) [`767e9fe`](https://github.com/gradio-app/trackio/commit/767e9fe095d7c6ed102016caf927c1517fb8618c) - tiny pr removing unnecessary code. Thanks @abidlabs!
55
+
56
+ ## 0.13.0
57
+
58
+ ### Features
59
+
60
+ - [#358](https://github.com/gradio-app/trackio/pull/358) [`073715d`](https://github.com/gradio-app/trackio/commit/073715d1caf8282f68890117f09c3ac301205312) - Improvements to `trackio.sync()`. Thanks @abidlabs!
61
+
62
+ ## 0.12.0
63
+
64
+ ### Features
65
+
66
+ - [#357](https://github.com/gradio-app/trackio/pull/357) [`02ba815`](https://github.com/gradio-app/trackio/commit/02ba815358060f1966052de051a5bdb09702920e) - Redesign media and tables to show up on separate page. Thanks @abidlabs!
67
+ - [#359](https://github.com/gradio-app/trackio/pull/359) [`08fe9c9`](https://github.com/gradio-app/trackio/commit/08fe9c9ddd7fe99ee811555fdfb62df9ab88e939) - docs: Improve docstrings. Thanks @qgallouedec!
68
+
69
+ ## 0.11.0
70
+
71
+ ### Features
72
+
73
+ - [#355](https://github.com/gradio-app/trackio/pull/355) [`ea51f49`](https://github.com/gradio-app/trackio/commit/ea51f4954922f21be76ef828700420fe9a912c4b) - Color code run checkboxes and match with plot lines. Thanks @abidlabs!
74
+ - [#353](https://github.com/gradio-app/trackio/pull/353) [`8abe691`](https://github.com/gradio-app/trackio/commit/8abe6919aeefe21fc7a23af814883efbb037c21f) - Remove show_api from demo.launch. Thanks @sergiopaniego!
75
+ - [#351](https://github.com/gradio-app/trackio/pull/351) [`8a8957e`](https://github.com/gradio-app/trackio/commit/8a8957e530dd7908d1fef7f2df030303f808101f) - Add `trackio.save()`. Thanks @abidlabs!
76
+
77
+ ## 0.10.0
78
+
79
+ ### Features
80
+
81
+ - [#305](https://github.com/gradio-app/trackio/pull/305) [`e64883a`](https://github.com/gradio-app/trackio/commit/e64883a51f7b8b93f7d48b8afe55acdb62238b71) - bump to gradio 6.0, make `trackio` compatible, and fix related issues. Thanks @abidlabs!
82
+
83
+ ## 0.9.1
84
+
85
+ ### Features
86
+
87
+ - [#344](https://github.com/gradio-app/trackio/pull/344) [`7e01024`](https://github.com/gradio-app/trackio/commit/7e010241d9a34794e0ce0dc19c1a6f0cf94ba856) - Avoid redundant calls to /whoami-v2. Thanks @Wauplin!
88
+
89
+ ## 0.9.0
90
+
91
+ ### Features
92
+
93
+ - [#343](https://github.com/gradio-app/trackio/pull/343) [`51bea30`](https://github.com/gradio-app/trackio/commit/51bea30f2877adff8e6497466d3a799400a0a049) - Sync offline projects to Hugging Face spaces. Thanks @candemircan!
94
+ - [#341](https://github.com/gradio-app/trackio/pull/341) [`4fd841f`](https://github.com/gradio-app/trackio/commit/4fd841fa190e15071b02f6fba7683ef4f393a654) - Adds a basic UI test to `trackio`. Thanks @abidlabs!
95
+ - [#339](https://github.com/gradio-app/trackio/pull/339) [`011d91b`](https://github.com/gradio-app/trackio/commit/011d91bb6ae266516fd250a349285670a8049d05) - Allow customzing the trackio color palette. Thanks @abidlabs!
96
+
97
+ ## 0.8.1
98
+
99
+ ### Features
100
+
101
+ - [#336](https://github.com/gradio-app/trackio/pull/336) [`5f9f51d`](https://github.com/gradio-app/trackio/commit/5f9f51dac8677f240d7c42c3e3b2660a22aee138) - Support a list of `Trackio.Image` in a `trackio.Table` cell. Thanks @abidlabs!
102
+
103
+ ## 0.8.0
104
+
105
+ ### Features
106
+
107
+ - [#331](https://github.com/gradio-app/trackio/pull/331) [`2c02d0f`](https://github.com/gradio-app/trackio/commit/2c02d0fd0a5824160528782402bb0dd4083396d5) - Truncate table string values that are greater than 250 characters (configuirable via env variable). Thanks @abidlabs!
108
+ - [#324](https://github.com/gradio-app/trackio/pull/324) [`50b2122`](https://github.com/gradio-app/trackio/commit/50b2122e7965ac82a72e6cb3b7d048bc10a2a6b1) - Add log y-axis functionality to UI. Thanks @abidlabs!
109
+ - [#326](https://github.com/gradio-app/trackio/pull/326) [`61dc1f4`](https://github.com/gradio-app/trackio/commit/61dc1f40af2f545f8e70395ddf0dbb8aee6b60d5) - Fix: improve table rendering for metrics in Trackio Dashboard. Thanks @vigneshwaran!
110
+ - [#328](https://github.com/gradio-app/trackio/pull/328) [`6857cbb`](https://github.com/gradio-app/trackio/commit/6857cbbe557a59a4642f210ec42566d108294e63) - Support trackio.Table with trackio.Image columns. Thanks @abidlabs!
111
+ - [#323](https://github.com/gradio-app/trackio/pull/323) [`6857cbb`](https://github.com/gradio-app/trackio/commit/6857cbbe557a59a4642f210ec42566d108294e63) - add Trackio client implementations in Go, Rust, and JS. Thanks @vaibhav-research!
112
+
113
+ ## 0.7.0
114
+
115
+ ### Features
116
+
117
+ - [#277](https://github.com/gradio-app/trackio/pull/277) [`db35601`](https://github.com/gradio-app/trackio/commit/db35601b9c023423c4654c9909b8ab73e58737de) - fix: make grouped runs view reflect live updates. Thanks @Saba9!
118
+ - [#320](https://github.com/gradio-app/trackio/pull/320) [`24ae739`](https://github.com/gradio-app/trackio/commit/24ae73969b09fb3126acd2f91647cdfbf8cf72a1) - Add additional query parms for xmin, xmax, and smoothing. Thanks @abidlabs!
119
+ - [#270](https://github.com/gradio-app/trackio/pull/270) [`cd1dfc3`](https://github.com/gradio-app/trackio/commit/cd1dfc3dc641b4499ac6d4a1b066fa8e2b52c57b) - feature: add support for logging audio. Thanks @Saba9!
120
+
121
+ ## 0.6.0
122
+
123
+ ### Features
124
+
125
+ - [#309](https://github.com/gradio-app/trackio/pull/309) [`1df2353`](https://github.com/gradio-app/trackio/commit/1df23534d6c01938c8db9c0f584ffa23e8d6021d) - Add histogram support with wandb-compatible API. Thanks @abidlabs!
126
+ - [#315](https://github.com/gradio-app/trackio/pull/315) [`76ba060`](https://github.com/gradio-app/trackio/commit/76ba06055dc43ca8f03b79f3e72d761949bd19a8) - Add guards to avoid silent fails. Thanks @Xmaster6y!
127
+ - [#313](https://github.com/gradio-app/trackio/pull/313) [`a606b3e`](https://github.com/gradio-app/trackio/commit/a606b3e1c5edf3d4cf9f31bd50605226a5a1c5d0) - No longer prevent certain keys from being used. Instead, dunderify them to prevent collisions with internal usage. Thanks @abidlabs!
128
+ - [#317](https://github.com/gradio-app/trackio/pull/317) [`27370a5`](https://github.com/gradio-app/trackio/commit/27370a595d0dbdf7eebbe7159d2ba778f039da44) - quick fixes for trackio.histogram. Thanks @abidlabs!
129
+ - [#312](https://github.com/gradio-app/trackio/pull/312) [`aa0f3bf`](https://github.com/gradio-app/trackio/commit/aa0f3bf372e7a0dd592a38af699c998363830eeb) - Fix video logging by adding TRACKIO_DIR to allowed_paths. Thanks @abidlabs!
130
+
131
+ ## 0.5.3
132
+
133
+ ### Features
134
+
135
+ - [#300](https://github.com/gradio-app/trackio/pull/300) [`5e4cacf`](https://github.com/gradio-app/trackio/commit/5e4cacf2e7ce527b4ce60de3a5bc05d2c02c77fb) - Adds more environment variables to allow customization of Trackio dashboard. Thanks @abidlabs!
136
+
137
+ ## 0.5.2
138
+
139
+ ### Features
140
+
141
+ - [#293](https://github.com/gradio-app/trackio/pull/293) [`64afc28`](https://github.com/gradio-app/trackio/commit/64afc28d3ea1dfd821472dc6bf0b8ed35a9b74be) - Ensures that the TRACKIO_DIR environment variable is respected. Thanks @abidlabs!
142
+ - [#287](https://github.com/gradio-app/trackio/pull/287) [`cd3e929`](https://github.com/gradio-app/trackio/commit/cd3e9294320949e6b8b829239069a43d5d7ff4c1) - fix(sqlite): unify .sqlite extension, allow export when DBs exist, clean WAL sidecars on import. Thanks @vaibhav-research!
143
+
144
+ ### Fixes
145
+
146
+ - [#291](https://github.com/gradio-app/trackio/pull/291) [`3b5adc3`](https://github.com/gradio-app/trackio/commit/3b5adc3d1f452dbab7a714d235f4974782f93730) - Fix the wheel build. Thanks @pngwn!
147
+
148
+ ## 0.5.1
149
+
150
+ ### Fixes
151
+
152
+ - [#278](https://github.com/gradio-app/trackio/pull/278) [`314c054`](https://github.com/gradio-app/trackio/commit/314c05438007ddfea3383e06fd19143e27468e2d) - Fix row orientation of metrics plots. Thanks @abidlabs!
trackio/__init__.py ADDED
@@ -0,0 +1,601 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import atexit
2
+ import glob
3
+ import json
4
+ import logging
5
+ import os
6
+ import shutil
7
+ import warnings
8
+ import webbrowser
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import huggingface_hub
13
+ from gradio.themes import ThemeClass
14
+ from gradio.utils import TupleNoPrint
15
+ from gradio_client import Client, handle_file
16
+ from huggingface_hub import SpaceStorage
17
+ from huggingface_hub.errors import LocalTokenNotFoundError
18
+
19
+ from trackio import context_vars, deploy, utils
20
+ from trackio.api import Api
21
+ from trackio.deploy import sync
22
+ from trackio.gpu import gpu_available, log_gpu
23
+ from trackio.histogram import Histogram
24
+ from trackio.imports import import_csv, import_tf_events
25
+ from trackio.media import (
26
+ TrackioAudio,
27
+ TrackioImage,
28
+ TrackioVideo,
29
+ get_project_media_path,
30
+ )
31
+ from trackio.run import Run
32
+ from trackio.sqlite_storage import SQLiteStorage
33
+ from trackio.table import Table
34
+ from trackio.typehints import UploadEntry
35
+ from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_DIR
36
+
37
+ logging.getLogger("httpx").setLevel(logging.WARNING)
38
+
39
+ warnings.filterwarnings(
40
+ "ignore",
41
+ message="Empty session being created. Install gradio\\[oauth\\]",
42
+ category=UserWarning,
43
+ module="gradio.helpers",
44
+ )
45
+
46
+ __version__ = json.loads(Path(__file__).parent.joinpath("package.json").read_text())[
47
+ "version"
48
+ ]
49
+
50
+ __all__ = [
51
+ "init",
52
+ "log",
53
+ "log_system",
54
+ "log_gpu",
55
+ "finish",
56
+ "show",
57
+ "sync",
58
+ "delete_project",
59
+ "import_csv",
60
+ "import_tf_events",
61
+ "save",
62
+ "Image",
63
+ "Video",
64
+ "Audio",
65
+ "Table",
66
+ "Histogram",
67
+ "Api",
68
+ ]
69
+
70
+ Image = TrackioImage
71
+ Video = TrackioVideo
72
+ Audio = TrackioAudio
73
+
74
+
75
+ config = {}
76
+
77
+ _atexit_registered = False
78
+
79
+
80
+ def _cleanup_current_run():
81
+ run = context_vars.current_run.get()
82
+ if run is not None:
83
+ try:
84
+ run.finish()
85
+ except Exception:
86
+ pass
87
+
88
+
89
+ def _get_demo():
90
+ # Lazy import to avoid initializing Gradio Blocks (and FastAPI) at import time,
91
+ # which causes import lock errors for libraries that just `import trackio`.
92
+ from trackio.ui.main import CSS, HEAD, demo
93
+
94
+ return demo, CSS, HEAD
95
+
96
+
97
+ def init(
98
+ project: str,
99
+ name: str | None = None,
100
+ group: str | None = None,
101
+ space_id: str | None = None,
102
+ space_storage: SpaceStorage | None = None,
103
+ dataset_id: str | None = None,
104
+ config: dict | None = None,
105
+ resume: str = "never",
106
+ settings: Any = None,
107
+ private: bool | None = None,
108
+ embed: bool = True,
109
+ auto_log_gpu: bool | None = None,
110
+ gpu_log_interval: float = 10.0,
111
+ ) -> Run:
112
+ """
113
+ Creates a new Trackio project and returns a [`Run`] object.
114
+
115
+ Args:
116
+ project (`str`):
117
+ The name of the project (can be an existing project to continue tracking or
118
+ a new project to start tracking from scratch).
119
+ name (`str`, *optional*):
120
+ The name of the run (if not provided, a default name will be generated).
121
+ group (`str`, *optional*):
122
+ The name of the group which this run belongs to in order to help organize
123
+ related runs together. You can toggle the entire group's visibilitiy in the
124
+ dashboard.
125
+ space_id (`str`, *optional*):
126
+ If provided, the project will be logged to a Hugging Face Space instead of
127
+ a local directory. Should be a complete Space name like
128
+ `"username/reponame"` or `"orgname/reponame"`, or just `"reponame"` in which
129
+ case the Space will be created in the currently-logged-in Hugging Face
130
+ user's namespace. If the Space does not exist, it will be created. If the
131
+ Space already exists, the project will be logged to it.
132
+ space_storage ([`~huggingface_hub.SpaceStorage`], *optional*):
133
+ Choice of persistent storage tier.
134
+ dataset_id (`str`, *optional*):
135
+ If a `space_id` is provided, a persistent Hugging Face Dataset will be
136
+ created and the metrics will be synced to it every 5 minutes. Specify a
137
+ Dataset with name like `"username/datasetname"` or `"orgname/datasetname"`,
138
+ or `"datasetname"` (uses currently-logged-in Hugging Face user's namespace),
139
+ or `None` (uses the same name as the Space but with the `"_dataset"`
140
+ suffix). If the Dataset does not exist, it will be created. If the Dataset
141
+ already exists, the project will be appended to it.
142
+ config (`dict`, *optional*):
143
+ A dictionary of configuration options. Provided for compatibility with
144
+ `wandb.init()`.
145
+ resume (`str`, *optional*, defaults to `"never"`):
146
+ Controls how to handle resuming a run. Can be one of:
147
+
148
+ - `"must"`: Must resume the run with the given name, raises error if run
149
+ doesn't exist
150
+ - `"allow"`: Resume the run if it exists, otherwise create a new run
151
+ - `"never"`: Never resume a run, always create a new one
152
+ private (`bool`, *optional*):
153
+ Whether to make the Space private. If None (default), the repo will be
154
+ public unless the organization's default is private. This value is ignored
155
+ if the repo already exists.
156
+ settings (`Any`, *optional*):
157
+ Not used. Provided for compatibility with `wandb.init()`.
158
+ embed (`bool`, *optional*, defaults to `True`):
159
+ If running inside a jupyter/Colab notebook, whether the dashboard should
160
+ automatically be embedded in the cell when trackio.init() is called.
161
+ auto_log_gpu (`bool` or `None`, *optional*, defaults to `None`):
162
+ Controls automatic GPU metrics logging. If `None` (default), GPU logging
163
+ is automatically enabled when `nvidia-ml-py` is installed and an NVIDIA
164
+ GPU is detected. Set to `True` to force enable or `False` to disable.
165
+ gpu_log_interval (`float`, *optional*, defaults to `10.0`):
166
+ The interval in seconds between automatic GPU metric logs.
167
+ Only used when `auto_log_gpu=True`.
168
+
169
+ Returns:
170
+ `Run`: A [`Run`] object that can be used to log metrics and finish the run.
171
+ """
172
+ if settings is not None:
173
+ warnings.warn(
174
+ "* Warning: settings is not used. Provided for compatibility with wandb.init(). Please create an issue at: https://github.com/gradio-app/trackio/issues if you need a specific feature implemented."
175
+ )
176
+
177
+ if space_id is None and dataset_id is not None:
178
+ raise ValueError("Must provide a `space_id` when `dataset_id` is provided.")
179
+ try:
180
+ space_id, dataset_id = utils.preprocess_space_and_dataset_ids(
181
+ space_id, dataset_id
182
+ )
183
+ except LocalTokenNotFoundError as e:
184
+ raise LocalTokenNotFoundError(
185
+ f"You must be logged in to Hugging Face locally when `space_id` is provided to deploy to a Space. {e}"
186
+ ) from e
187
+
188
+ url = context_vars.current_server.get()
189
+
190
+ if space_id is not None:
191
+ if url is None:
192
+ url = space_id
193
+ context_vars.current_server.set(url)
194
+ context_vars.current_space_id.set(space_id)
195
+
196
+ if (
197
+ context_vars.current_project.get() is None
198
+ or context_vars.current_project.get() != project
199
+ ):
200
+ print(f"* Trackio project initialized: {project}")
201
+
202
+ if dataset_id is not None:
203
+ os.environ["TRACKIO_DATASET_ID"] = dataset_id
204
+ print(
205
+ f"* Trackio metrics will be synced to Hugging Face Dataset: {dataset_id}"
206
+ )
207
+ if space_id is None:
208
+ print(f"* Trackio metrics logged to: {TRACKIO_DIR}")
209
+ utils.print_dashboard_instructions(project)
210
+ else:
211
+ deploy.create_space_if_not_exists(
212
+ space_id, space_storage, dataset_id, private
213
+ )
214
+ user_name, space_name = space_id.split("/")
215
+ space_url = deploy.SPACE_HOST_URL.format(
216
+ user_name=user_name, space_name=space_name
217
+ )
218
+ print(f"* View dashboard by going to: {space_url}")
219
+ if utils.is_in_notebook() and embed:
220
+ utils.embed_url_in_notebook(space_url)
221
+ context_vars.current_project.set(project)
222
+
223
+ if resume == "must":
224
+ if name is None:
225
+ raise ValueError("Must provide a run name when resume='must'")
226
+ if name not in SQLiteStorage.get_runs(project):
227
+ raise ValueError(f"Run '{name}' does not exist in project '{project}'")
228
+ resumed = True
229
+ elif resume == "allow":
230
+ resumed = name is not None and name in SQLiteStorage.get_runs(project)
231
+ elif resume == "never":
232
+ if name is not None and name in SQLiteStorage.get_runs(project):
233
+ warnings.warn(
234
+ f"* Warning: resume='never' but a run '{name}' already exists in "
235
+ f"project '{project}'. Generating a new name and instead. If you want "
236
+ "to resume this run, call init() with resume='must' or resume='allow'."
237
+ )
238
+ name = None
239
+ resumed = False
240
+ else:
241
+ raise ValueError("resume must be one of: 'must', 'allow', or 'never'")
242
+
243
+ if auto_log_gpu is None:
244
+ auto_log_gpu = gpu_available()
245
+ if auto_log_gpu:
246
+ print("* GPU detected, enabling automatic GPU metrics logging")
247
+
248
+ run = Run(
249
+ url=url,
250
+ project=project,
251
+ client=None,
252
+ name=name,
253
+ group=group,
254
+ config=config,
255
+ space_id=space_id,
256
+ auto_log_gpu=auto_log_gpu,
257
+ gpu_log_interval=gpu_log_interval,
258
+ )
259
+
260
+ if space_id is not None:
261
+ SQLiteStorage.set_project_metadata(project, "space_id", space_id)
262
+ if SQLiteStorage.has_pending_data(project):
263
+ run._has_local_buffer = True
264
+
265
+ global _atexit_registered
266
+ if not _atexit_registered:
267
+ atexit.register(_cleanup_current_run)
268
+ _atexit_registered = True
269
+
270
+ if resumed:
271
+ print(f"* Resumed existing run: {run.name}")
272
+ else:
273
+ print(f"* Created new run: {run.name}")
274
+
275
+ context_vars.current_run.set(run)
276
+ globals()["config"] = run.config
277
+ return run
278
+
279
+
280
+ def log(metrics: dict, step: int | None = None) -> None:
281
+ """
282
+ Logs metrics to the current run.
283
+
284
+ Args:
285
+ metrics (`dict`):
286
+ A dictionary of metrics to log.
287
+ step (`int`, *optional*):
288
+ The step number. If not provided, the step will be incremented
289
+ automatically.
290
+ """
291
+ run = context_vars.current_run.get()
292
+ if run is None:
293
+ raise RuntimeError("Call trackio.init() before trackio.log().")
294
+ run.log(
295
+ metrics=metrics,
296
+ step=step,
297
+ )
298
+
299
+
300
+ def log_system(metrics: dict) -> None:
301
+ """
302
+ Logs system metrics (GPU, etc.) to the current run using timestamps instead of steps.
303
+
304
+ Args:
305
+ metrics (`dict`):
306
+ A dictionary of system metrics to log.
307
+ """
308
+ run = context_vars.current_run.get()
309
+ if run is None:
310
+ raise RuntimeError("Call trackio.init() before trackio.log_system().")
311
+ run.log_system(metrics=metrics)
312
+
313
+
314
+ def finish():
315
+ """
316
+ Finishes the current run.
317
+ """
318
+ run = context_vars.current_run.get()
319
+ if run is None:
320
+ raise RuntimeError("Call trackio.init() before trackio.finish().")
321
+ run.finish()
322
+
323
+
324
+ def delete_project(project: str, force: bool = False) -> bool:
325
+ """
326
+ Deletes a project by removing its local SQLite database.
327
+
328
+ Args:
329
+ project (`str`):
330
+ The name of the project to delete.
331
+ force (`bool`, *optional*, defaults to `False`):
332
+ If `True`, deletes the project without prompting for confirmation.
333
+ If `False`, prompts the user to confirm before deleting.
334
+
335
+ Returns:
336
+ `bool`: `True` if the project was deleted, `False` otherwise.
337
+ """
338
+ db_path = SQLiteStorage.get_project_db_path(project)
339
+
340
+ if not db_path.exists():
341
+ print(f"* Project '{project}' does not exist.")
342
+ return False
343
+
344
+ if not force:
345
+ response = input(
346
+ f"Are you sure you want to delete project '{project}'? "
347
+ f"This will permanently delete all runs and metrics. (y/N): "
348
+ )
349
+ if response.lower() not in ["y", "yes"]:
350
+ print("* Deletion cancelled.")
351
+ return False
352
+
353
+ try:
354
+ db_path.unlink()
355
+
356
+ for suffix in ("-wal", "-shm"):
357
+ sidecar = Path(str(db_path) + suffix)
358
+ if sidecar.exists():
359
+ sidecar.unlink()
360
+
361
+ print(f"* Project '{project}' has been deleted.")
362
+ return True
363
+ except Exception as e:
364
+ print(f"* Error deleting project '{project}': {e}")
365
+ return False
366
+
367
+
368
+ def save(
369
+ glob_str: str | Path,
370
+ project: str | None = None,
371
+ ) -> str:
372
+ """
373
+ Saves files to a project (not linked to a specific run). If Trackio is running
374
+ locally, the file(s) will be copied to the project's files directory. If Trackio is
375
+ running in a Space, the file(s) will be uploaded to the Space's files directory.
376
+
377
+ Args:
378
+ glob_str (`str` or `Path`):
379
+ The file path or glob pattern to save. Can be a single file or a pattern
380
+ matching multiple files (e.g., `"*.py"`, `"models/**/*.pth"`).
381
+ project (`str`, *optional*):
382
+ The name of the project to save files to. If not provided, uses the current
383
+ project from `trackio.init()`. If no project is initialized, raises an
384
+ error.
385
+
386
+ Returns:
387
+ `str`: The path where the file(s) were saved (project's files directory).
388
+
389
+ Example:
390
+ ```python
391
+ import trackio
392
+
393
+ trackio.init(project="my-project")
394
+ trackio.save("config.yaml")
395
+ trackio.save("models/*.pth")
396
+ ```
397
+ """
398
+ if project is None:
399
+ project = context_vars.current_project.get()
400
+ if project is None:
401
+ raise RuntimeError(
402
+ "No project specified. Either call trackio.init() first or provide a "
403
+ "project parameter to trackio.save()."
404
+ )
405
+
406
+ glob_str = Path(glob_str)
407
+ base_path = Path.cwd().resolve()
408
+
409
+ matched_files = []
410
+ if glob_str.is_file():
411
+ matched_files = [glob_str.resolve()]
412
+ else:
413
+ pattern = str(glob_str)
414
+ if not glob_str.is_absolute():
415
+ pattern = str((Path.cwd() / glob_str).resolve())
416
+ matched_files = [
417
+ Path(f).resolve()
418
+ for f in glob.glob(pattern, recursive=True)
419
+ if Path(f).is_file()
420
+ ]
421
+
422
+ if not matched_files:
423
+ raise ValueError(f"No files found matching pattern: {glob_str}")
424
+
425
+ current_run = context_vars.current_run.get()
426
+ is_local = (
427
+ current_run._is_local
428
+ if current_run is not None
429
+ else (context_vars.current_space_id.get() is None)
430
+ )
431
+
432
+ if is_local:
433
+ for file_path in matched_files:
434
+ try:
435
+ relative_to_base = file_path.relative_to(base_path)
436
+ except ValueError:
437
+ relative_to_base = Path(file_path.name)
438
+
439
+ if current_run is not None:
440
+ current_run._queue_upload(
441
+ file_path,
442
+ step=None,
443
+ relative_path=str(relative_to_base.parent),
444
+ use_run_name=False,
445
+ )
446
+ else:
447
+ media_path = get_project_media_path(
448
+ project=project,
449
+ run=None,
450
+ step=None,
451
+ relative_path=str(relative_to_base),
452
+ )
453
+ shutil.copy(str(file_path), str(media_path))
454
+ else:
455
+ url = context_vars.current_server.get()
456
+
457
+ upload_entries = []
458
+ for file_path in matched_files:
459
+ try:
460
+ relative_to_base = file_path.relative_to(base_path)
461
+ except ValueError:
462
+ relative_to_base = Path(file_path.name)
463
+
464
+ if current_run is not None:
465
+ current_run._queue_upload(
466
+ file_path,
467
+ step=None,
468
+ relative_path=str(relative_to_base.parent),
469
+ use_run_name=False,
470
+ )
471
+ else:
472
+ upload_entry: UploadEntry = {
473
+ "project": project,
474
+ "run": None,
475
+ "step": None,
476
+ "relative_path": str(relative_to_base),
477
+ "uploaded_file": handle_file(file_path),
478
+ }
479
+ upload_entries.append(upload_entry)
480
+
481
+ if upload_entries:
482
+ if url is None:
483
+ raise RuntimeError(
484
+ "No server available. Call trackio.init() before trackio.save() to start the server."
485
+ )
486
+
487
+ try:
488
+ client = Client(url, verbose=False, httpx_kwargs={"timeout": 90})
489
+ client.predict(
490
+ api_name="/bulk_upload_media",
491
+ uploads=upload_entries,
492
+ hf_token=huggingface_hub.utils.get_token(),
493
+ )
494
+ except Exception as e:
495
+ warnings.warn(
496
+ f"Failed to upload files: {e}. "
497
+ "Files may not be available in the dashboard."
498
+ )
499
+
500
+ return str(utils.MEDIA_DIR / project / "files")
501
+
502
+
503
+ def show(
504
+ project: str | None = None,
505
+ *,
506
+ theme: str | ThemeClass | None = None,
507
+ mcp_server: bool | None = None,
508
+ footer: bool = True,
509
+ color_palette: list[str] | None = None,
510
+ open_browser: bool = True,
511
+ block_thread: bool | None = None,
512
+ host: str | None = None,
513
+ ):
514
+ """
515
+ Launches the Trackio dashboard.
516
+
517
+ Args:
518
+ project (`str`, *optional*):
519
+ The name of the project whose runs to show. If not provided, all projects
520
+ will be shown and the user can select one.
521
+ theme (`str` or `ThemeClass`, *optional*):
522
+ A Gradio Theme to use for the dashboard instead of the default Gradio theme,
523
+ can be a built-in theme (e.g. `'soft'`, `'citrus'`), a theme from the Hub
524
+ (e.g. `"gstaff/xkcd"`), or a custom Theme class. If not provided, the
525
+ `TRACKIO_THEME` environment variable will be used, or if that is not set,
526
+ the default Gradio theme will be used.
527
+ mcp_server (`bool`, *optional*):
528
+ If `True`, the Trackio dashboard will be set up as an MCP server and certain
529
+ functions will be added as MCP tools. If `None` (default behavior), then the
530
+ `GRADIO_MCP_SERVER` environment variable will be used to determine if the
531
+ MCP server should be enabled (which is `"True"` on Hugging Face Spaces).
532
+ footer (`bool`, *optional*, defaults to `True`):
533
+ Whether to show the Gradio footer. When `False`, the footer will be hidden.
534
+ This can also be controlled via the `footer` query parameter in the URL.
535
+ color_palette (`list[str]`, *optional*):
536
+ A list of hex color codes to use for plot lines. If not provided, the
537
+ `TRACKIO_COLOR_PALETTE` environment variable will be used (comma-separated
538
+ hex codes), or if that is not set, the default color palette will be used.
539
+ Example: `['#FF0000', '#00FF00', '#0000FF']`
540
+ open_browser (`bool`, *optional*, defaults to `True`):
541
+ If `True` and not in a notebook, a new browser tab will be opened with the
542
+ dashboard. If `False`, the browser will not be opened.
543
+ block_thread (`bool`, *optional*):
544
+ If `True`, the main thread will be blocked until the dashboard is closed.
545
+ If `None` (default behavior), then the main thread will not be blocked if the
546
+ dashboard is launched in a notebook, otherwise the main thread will be blocked.
547
+ host (`str`, *optional*):
548
+ The host to bind the server to. If not provided, defaults to `'127.0.0.1'`
549
+ (localhost only). Set to `'0.0.0.0'` to allow remote access.
550
+
551
+ Returns:
552
+ `app`: The Gradio app object corresponding to the dashboard launched by Trackio.
553
+ `url`: The local URL of the dashboard.
554
+ `share_url`: The public share URL of the dashboard.
555
+ `full_url`: The full URL of the dashboard including the write token (will use the public share URL if launched publicly, otherwise the local URL).
556
+ """
557
+ demo, CSS, HEAD = _get_demo()
558
+
559
+ if color_palette is not None:
560
+ os.environ["TRACKIO_COLOR_PALETTE"] = ",".join(color_palette)
561
+
562
+ theme = theme or os.environ.get("TRACKIO_THEME")
563
+
564
+ _mcp_server = (
565
+ mcp_server
566
+ if mcp_server is not None
567
+ else os.environ.get("GRADIO_MCP_SERVER", "False") == "True"
568
+ )
569
+
570
+ app, url, share_url = demo.launch(
571
+ css=CSS,
572
+ head=HEAD,
573
+ footer_links=["gradio", "settings"] + (["api"] if _mcp_server else []),
574
+ quiet=True,
575
+ inline=False,
576
+ prevent_thread_lock=True,
577
+ favicon_path=TRACKIO_LOGO_DIR / "trackio_logo_light.png",
578
+ allowed_paths=[TRACKIO_LOGO_DIR, TRACKIO_DIR],
579
+ mcp_server=_mcp_server,
580
+ theme=theme,
581
+ ssr_mode=False,
582
+ server_name=host,
583
+ )
584
+
585
+ base_url = share_url + "/" if share_url else url
586
+ full_url = utils.get_full_url(
587
+ base_url, project=project, write_token=demo.write_token, footer=footer
588
+ )
589
+
590
+ if not utils.is_in_notebook():
591
+ print(f"* Trackio UI launched at: {full_url}")
592
+ if open_browser:
593
+ webbrowser.open(full_url)
594
+ block_thread = block_thread if block_thread is not None else True
595
+ else:
596
+ utils.embed_url_in_notebook(full_url)
597
+ block_thread = block_thread if block_thread is not None else False
598
+
599
+ if block_thread:
600
+ utils.block_main_thread_until_keyboard_interrupt()
601
+ return TupleNoPrint((demo, url, share_url, full_url))
trackio/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (24.5 kB). View file
 
trackio/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (12.9 kB). View file
 
trackio/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (24.9 kB). View file
 
trackio/__pycache__/api.cpython-312.pyc ADDED
Binary file (4.42 kB). View file
 
trackio/__pycache__/cli.cpython-312.pyc ADDED
Binary file (17.9 kB). View file
 
trackio/__pycache__/cli_helpers.cpython-312.pyc ADDED
Binary file (5.88 kB). View file
 
trackio/__pycache__/commit_scheduler.cpython-312.pyc ADDED
Binary file (14.3 kB). View file
 
trackio/__pycache__/commit_scheduler.cpython-313.pyc ADDED
Binary file (18.3 kB). View file
 
trackio/__pycache__/context_vars.cpython-312.pyc ADDED
Binary file (913 Bytes). View file
 
trackio/__pycache__/context_vars.cpython-313.pyc ADDED
Binary file (745 Bytes). View file
 
trackio/__pycache__/deploy.cpython-312.pyc ADDED
Binary file (18.9 kB). View file
 
trackio/__pycache__/deploy.cpython-313.pyc ADDED
Binary file (6.27 kB). View file
 
trackio/__pycache__/dummy_commit_scheduler.cpython-312.pyc ADDED
Binary file (1.01 kB). View file
 
trackio/__pycache__/dummy_commit_scheduler.cpython-313.pyc ADDED
Binary file (1.1 kB). View file
 
trackio/__pycache__/file_storage.cpython-312.pyc ADDED
Binary file (1.63 kB). View file
 
trackio/__pycache__/gpu.cpython-312.pyc ADDED
Binary file (14.3 kB). View file
 
trackio/__pycache__/histogram.cpython-312.pyc ADDED
Binary file (3.22 kB). View file
 
trackio/__pycache__/imports.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
trackio/__pycache__/imports.cpython-313.pyc ADDED
Binary file (11.6 kB). View file
 
trackio/__pycache__/media.cpython-312.pyc ADDED
Binary file (15 kB). View file
 
trackio/__pycache__/media_commit_scheduler.cpython-312.pyc ADDED
Binary file (3.66 kB). View file
 
trackio/__pycache__/run.cpython-312.pyc ADDED
Binary file (27.1 kB). View file
 
trackio/__pycache__/run.cpython-313.pyc ADDED
Binary file (1.37 kB). View file
 
trackio/__pycache__/sqlite_storage.cpython-312.pyc ADDED
Binary file (63.3 kB). View file
 
trackio/__pycache__/sqlite_storage.cpython-313.pyc ADDED
Binary file (13.8 kB). View file
 
trackio/__pycache__/sqlite_types.cpython-312.pyc ADDED
Binary file (1.35 kB). View file
 
trackio/__pycache__/table.cpython-312.pyc ADDED
Binary file (8.59 kB). View file
 
trackio/__pycache__/typehints.cpython-312.pyc ADDED
Binary file (1.33 kB). View file
 
trackio/__pycache__/ui.cpython-312.pyc ADDED
Binary file (30.7 kB). View file
 
trackio/__pycache__/ui.cpython-313.pyc ADDED
Binary file (5.37 kB). View file
 
trackio/__pycache__/utils.cpython-312.pyc ADDED
Binary file (29.9 kB). View file
 
trackio/__pycache__/utils.cpython-313.pyc ADDED
Binary file (9.8 kB). View file
 
trackio/__pycache__/video_writer.cpython-312.pyc ADDED
Binary file (5.32 kB). View file
 
trackio/api.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Iterator
2
+
3
+ from trackio.sqlite_storage import SQLiteStorage
4
+
5
+
6
+ class Run:
7
+ def __init__(self, project: str, name: str):
8
+ self.project = project
9
+ self.name = name
10
+ self._config = None
11
+
12
+ @property
13
+ def id(self) -> str:
14
+ return self.name
15
+
16
+ @property
17
+ def config(self) -> dict | None:
18
+ if self._config is None:
19
+ self._config = SQLiteStorage.get_run_config(self.project, self.name)
20
+ return self._config
21
+
22
+ def delete(self) -> bool:
23
+ return SQLiteStorage.delete_run(self.project, self.name)
24
+
25
+ def move(self, new_project: str) -> bool:
26
+ success = SQLiteStorage.move_run(self.project, self.name, new_project)
27
+ if success:
28
+ self.project = new_project
29
+ return success
30
+
31
+ def __repr__(self) -> str:
32
+ return f"<Run {self.name} in project {self.project}>"
33
+
34
+
35
+ class Runs:
36
+ def __init__(self, project: str):
37
+ self.project = project
38
+ self._runs = None
39
+
40
+ def _load_runs(self):
41
+ if self._runs is None:
42
+ run_names = SQLiteStorage.get_runs(self.project)
43
+ self._runs = [Run(self.project, name) for name in run_names]
44
+
45
+ def __iter__(self) -> Iterator[Run]:
46
+ self._load_runs()
47
+ return iter(self._runs)
48
+
49
+ def __getitem__(self, index: int) -> Run:
50
+ self._load_runs()
51
+ return self._runs[index]
52
+
53
+ def __len__(self) -> int:
54
+ self._load_runs()
55
+ return len(self._runs)
56
+
57
+ def __repr__(self) -> str:
58
+ self._load_runs()
59
+ return f"<Runs project={self.project} count={len(self._runs)}>"
60
+
61
+
62
+ class Api:
63
+ def runs(self, project: str) -> Runs:
64
+ if not SQLiteStorage.get_project_db_path(project).exists():
65
+ raise ValueError(f"Project '{project}' does not exist")
66
+ return Runs(project)
trackio/assets/badge.png ADDED
trackio/assets/trackio_logo_dark.png ADDED
trackio/assets/trackio_logo_light.png ADDED
trackio/assets/trackio_logo_old.png ADDED

Git LFS Details

  • SHA256: 3922c4d1e465270ad4d8abb12023f3beed5d9f7f338528a4c0ac21dcf358a1c8
  • Pointer size: 131 Bytes
  • Size of remote file: 487 kB
trackio/assets/trackio_logo_type_dark.png ADDED
trackio/assets/trackio_logo_type_dark_transparent.png ADDED
trackio/assets/trackio_logo_type_light.png ADDED
trackio/assets/trackio_logo_type_light_transparent.png ADDED
trackio/cli.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ from trackio import show, sync
4
+ from trackio.cli_helpers import (
5
+ error_exit,
6
+ format_json,
7
+ format_list,
8
+ format_metric_values,
9
+ format_project_summary,
10
+ format_run_summary,
11
+ format_system_metric_names,
12
+ format_system_metrics,
13
+ )
14
+ from trackio.sqlite_storage import SQLiteStorage
15
+ from trackio.ui.main import get_project_summary, get_run_summary
16
+
17
+
18
+ def _handle_status():
19
+ print("Reading local Trackio projects...\n")
20
+ projects = SQLiteStorage.get_projects()
21
+ if not projects:
22
+ print("No Trackio projects found.")
23
+ return
24
+
25
+ local_projects = []
26
+ synced_projects = []
27
+ unsynced_projects = []
28
+
29
+ for project in projects:
30
+ space_id = SQLiteStorage.get_space_id(project)
31
+ if space_id is None:
32
+ local_projects.append(project)
33
+ elif SQLiteStorage.has_pending_data(project):
34
+ unsynced_projects.append(project)
35
+ else:
36
+ synced_projects.append(project)
37
+
38
+ print("Finished reading Trackio projects")
39
+ if local_projects:
40
+ print(f" * {len(local_projects)} local trackio project(s) [OK]")
41
+ if synced_projects:
42
+ print(f" * {len(synced_projects)} trackio project(s) synced to Spaces [OK]")
43
+ if unsynced_projects:
44
+ print(
45
+ f" * {len(unsynced_projects)} trackio project(s) with unsynced changes [WARNING]:"
46
+ )
47
+ for p in unsynced_projects:
48
+ print(f" - {p}")
49
+
50
+ if unsynced_projects:
51
+ print(
52
+ f"\nRun `trackio sync --project {unsynced_projects[0]}` to sync. "
53
+ "Or run `trackio sync --all` to sync all unsynced changes."
54
+ )
55
+
56
+
57
+ def _handle_sync(args):
58
+ from trackio.deploy import sync_incremental
59
+
60
+ if args.sync_all and args.project:
61
+ error_exit("Cannot use --all and --project together.")
62
+ if not args.sync_all and not args.project:
63
+ error_exit("Must provide either --project or --all.")
64
+
65
+ if args.sync_all:
66
+ projects = SQLiteStorage.get_projects()
67
+ synced_any = False
68
+ for project in projects:
69
+ space_id = SQLiteStorage.get_space_id(project)
70
+ if space_id and SQLiteStorage.has_pending_data(project):
71
+ sync_incremental(
72
+ project, space_id, private=args.private, pending_only=True
73
+ )
74
+ synced_any = True
75
+ if not synced_any:
76
+ print("No projects with unsynced data found.")
77
+ else:
78
+ space_id = args.space_id
79
+ if space_id is None:
80
+ space_id = SQLiteStorage.get_space_id(args.project)
81
+ sync(
82
+ project=args.project,
83
+ space_id=space_id,
84
+ private=args.private,
85
+ force=args.force,
86
+ )
87
+
88
+
89
+ def main():
90
+ parser = argparse.ArgumentParser(description="Trackio CLI")
91
+ subparsers = parser.add_subparsers(dest="command")
92
+
93
+ ui_parser = subparsers.add_parser(
94
+ "show", help="Show the Trackio dashboard UI for a project"
95
+ )
96
+ ui_parser.add_argument(
97
+ "--project", required=False, help="Project name to show in the dashboard"
98
+ )
99
+ ui_parser.add_argument(
100
+ "--theme",
101
+ required=False,
102
+ default="default",
103
+ help="A Gradio Theme to use for the dashboard instead of the default, can be a built-in theme (e.g. 'soft', 'citrus'), or a theme from the Hub (e.g. 'gstaff/xkcd').",
104
+ )
105
+ ui_parser.add_argument(
106
+ "--mcp-server",
107
+ action="store_true",
108
+ help="Enable MCP server functionality. The Trackio dashboard will be set up as an MCP server and certain functions will be exposed as MCP tools.",
109
+ )
110
+ ui_parser.add_argument(
111
+ "--footer",
112
+ action="store_true",
113
+ default=True,
114
+ help="Show the Gradio footer. Use --no-footer to hide it.",
115
+ )
116
+ ui_parser.add_argument(
117
+ "--no-footer",
118
+ dest="footer",
119
+ action="store_false",
120
+ help="Hide the Gradio footer.",
121
+ )
122
+ ui_parser.add_argument(
123
+ "--color-palette",
124
+ required=False,
125
+ help="Comma-separated list of hex color codes for plot lines (e.g. '#FF0000,#00FF00,#0000FF'). If not provided, the TRACKIO_COLOR_PALETTE environment variable will be used, or the default palette if not set.",
126
+ )
127
+ ui_parser.add_argument(
128
+ "--host",
129
+ required=False,
130
+ help="Host to bind the server to (e.g. '0.0.0.0' for remote access). If not provided, defaults to '127.0.0.1' (localhost only).",
131
+ )
132
+
133
+ subparsers.add_parser(
134
+ "status",
135
+ help="Show the status of all local Trackio projects, including sync status.",
136
+ )
137
+
138
+ sync_parser = subparsers.add_parser(
139
+ "sync",
140
+ help="Sync a local project's database to a Hugging Face Space. If the Space does not exist, it will be created.",
141
+ )
142
+ sync_parser.add_argument(
143
+ "--project",
144
+ required=False,
145
+ help="The name of the local project.",
146
+ )
147
+ sync_parser.add_argument(
148
+ "--space-id",
149
+ required=False,
150
+ help="The Hugging Face Space ID where the project will be synced (e.g. username/space_id). If not provided, uses the previously-configured Space.",
151
+ )
152
+ sync_parser.add_argument(
153
+ "--all",
154
+ action="store_true",
155
+ dest="sync_all",
156
+ help="Sync all projects that have unsynced data to their configured Spaces.",
157
+ )
158
+ sync_parser.add_argument(
159
+ "--private",
160
+ action="store_true",
161
+ help="Make the Hugging Face Space private if creating a new Space. By default, the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.",
162
+ )
163
+ sync_parser.add_argument(
164
+ "--force",
165
+ action="store_true",
166
+ help="Overwrite the existing database without prompting for confirmation.",
167
+ )
168
+
169
+ list_parser = subparsers.add_parser(
170
+ "list",
171
+ help="List projects, runs, or metrics",
172
+ )
173
+ list_subparsers = list_parser.add_subparsers(dest="list_type", required=True)
174
+
175
+ list_projects_parser = list_subparsers.add_parser(
176
+ "projects",
177
+ help="List all projects",
178
+ )
179
+ list_projects_parser.add_argument(
180
+ "--json",
181
+ action="store_true",
182
+ help="Output in JSON format",
183
+ )
184
+
185
+ list_runs_parser = list_subparsers.add_parser(
186
+ "runs",
187
+ help="List runs for a project",
188
+ )
189
+ list_runs_parser.add_argument(
190
+ "--project",
191
+ required=True,
192
+ help="Project name",
193
+ )
194
+ list_runs_parser.add_argument(
195
+ "--json",
196
+ action="store_true",
197
+ help="Output in JSON format",
198
+ )
199
+
200
+ list_metrics_parser = list_subparsers.add_parser(
201
+ "metrics",
202
+ help="List metrics for a run",
203
+ )
204
+ list_metrics_parser.add_argument(
205
+ "--project",
206
+ required=True,
207
+ help="Project name",
208
+ )
209
+ list_metrics_parser.add_argument(
210
+ "--run",
211
+ required=True,
212
+ help="Run name",
213
+ )
214
+ list_metrics_parser.add_argument(
215
+ "--json",
216
+ action="store_true",
217
+ help="Output in JSON format",
218
+ )
219
+
220
+ list_system_metrics_parser = list_subparsers.add_parser(
221
+ "system-metrics",
222
+ help="List system metrics for a run",
223
+ )
224
+ list_system_metrics_parser.add_argument(
225
+ "--project",
226
+ required=True,
227
+ help="Project name",
228
+ )
229
+ list_system_metrics_parser.add_argument(
230
+ "--run",
231
+ required=True,
232
+ help="Run name",
233
+ )
234
+ list_system_metrics_parser.add_argument(
235
+ "--json",
236
+ action="store_true",
237
+ help="Output in JSON format",
238
+ )
239
+
240
+ get_parser = subparsers.add_parser(
241
+ "get",
242
+ help="Get project, run, or metric information",
243
+ )
244
+ get_subparsers = get_parser.add_subparsers(dest="get_type", required=True)
245
+
246
+ get_project_parser = get_subparsers.add_parser(
247
+ "project",
248
+ help="Get project summary",
249
+ )
250
+ get_project_parser.add_argument(
251
+ "--project",
252
+ required=True,
253
+ help="Project name",
254
+ )
255
+ get_project_parser.add_argument(
256
+ "--json",
257
+ action="store_true",
258
+ help="Output in JSON format",
259
+ )
260
+
261
+ get_run_parser = get_subparsers.add_parser(
262
+ "run",
263
+ help="Get run summary",
264
+ )
265
+ get_run_parser.add_argument(
266
+ "--project",
267
+ required=True,
268
+ help="Project name",
269
+ )
270
+ get_run_parser.add_argument(
271
+ "--run",
272
+ required=True,
273
+ help="Run name",
274
+ )
275
+ get_run_parser.add_argument(
276
+ "--json",
277
+ action="store_true",
278
+ help="Output in JSON format",
279
+ )
280
+
281
+ get_metric_parser = get_subparsers.add_parser(
282
+ "metric",
283
+ help="Get metric values for a run",
284
+ )
285
+ get_metric_parser.add_argument(
286
+ "--project",
287
+ required=True,
288
+ help="Project name",
289
+ )
290
+ get_metric_parser.add_argument(
291
+ "--run",
292
+ required=True,
293
+ help="Run name",
294
+ )
295
+ get_metric_parser.add_argument(
296
+ "--metric",
297
+ required=True,
298
+ help="Metric name",
299
+ )
300
+ get_metric_parser.add_argument(
301
+ "--json",
302
+ action="store_true",
303
+ help="Output in JSON format",
304
+ )
305
+
306
+ get_system_metric_parser = get_subparsers.add_parser(
307
+ "system-metric",
308
+ help="Get system metric values for a run",
309
+ )
310
+ get_system_metric_parser.add_argument(
311
+ "--project",
312
+ required=True,
313
+ help="Project name",
314
+ )
315
+ get_system_metric_parser.add_argument(
316
+ "--run",
317
+ required=True,
318
+ help="Run name",
319
+ )
320
+ get_system_metric_parser.add_argument(
321
+ "--metric",
322
+ required=False,
323
+ help="System metric name (optional, if not provided returns all system metrics)",
324
+ )
325
+ get_system_metric_parser.add_argument(
326
+ "--json",
327
+ action="store_true",
328
+ help="Output in JSON format",
329
+ )
330
+
331
+ args = parser.parse_args()
332
+
333
+ if args.command == "show":
334
+ color_palette = None
335
+ if args.color_palette:
336
+ color_palette = [color.strip() for color in args.color_palette.split(",")]
337
+ show(
338
+ project=args.project,
339
+ theme=args.theme,
340
+ mcp_server=args.mcp_server,
341
+ footer=args.footer,
342
+ color_palette=color_palette,
343
+ host=args.host,
344
+ )
345
+ elif args.command == "status":
346
+ _handle_status()
347
+ elif args.command == "sync":
348
+ _handle_sync(args)
349
+ elif args.command == "list":
350
+ if args.list_type == "projects":
351
+ projects = SQLiteStorage.get_projects()
352
+ if args.json:
353
+ print(format_json({"projects": projects}))
354
+ else:
355
+ print(format_list(projects, "Projects"))
356
+ elif args.list_type == "runs":
357
+ db_path = SQLiteStorage.get_project_db_path(args.project)
358
+ if not db_path.exists():
359
+ error_exit(f"Project '{args.project}' not found.")
360
+ runs = SQLiteStorage.get_runs(args.project)
361
+ if args.json:
362
+ print(format_json({"project": args.project, "runs": runs}))
363
+ else:
364
+ print(format_list(runs, f"Runs in '{args.project}'"))
365
+ elif args.list_type == "metrics":
366
+ db_path = SQLiteStorage.get_project_db_path(args.project)
367
+ if not db_path.exists():
368
+ error_exit(f"Project '{args.project}' not found.")
369
+ runs = SQLiteStorage.get_runs(args.project)
370
+ if args.run not in runs:
371
+ error_exit(f"Run '{args.run}' not found in project '{args.project}'.")
372
+ metrics = SQLiteStorage.get_all_metrics_for_run(args.project, args.run)
373
+ if args.json:
374
+ print(
375
+ format_json(
376
+ {"project": args.project, "run": args.run, "metrics": metrics}
377
+ )
378
+ )
379
+ else:
380
+ print(
381
+ format_list(
382
+ metrics, f"Metrics for '{args.run}' in '{args.project}'"
383
+ )
384
+ )
385
+ elif args.list_type == "system-metrics":
386
+ db_path = SQLiteStorage.get_project_db_path(args.project)
387
+ if not db_path.exists():
388
+ error_exit(f"Project '{args.project}' not found.")
389
+ runs = SQLiteStorage.get_runs(args.project)
390
+ if args.run not in runs:
391
+ error_exit(f"Run '{args.run}' not found in project '{args.project}'.")
392
+ system_metrics = SQLiteStorage.get_all_system_metrics_for_run(
393
+ args.project, args.run
394
+ )
395
+ if args.json:
396
+ print(
397
+ format_json(
398
+ {
399
+ "project": args.project,
400
+ "run": args.run,
401
+ "system_metrics": system_metrics,
402
+ }
403
+ )
404
+ )
405
+ else:
406
+ print(format_system_metric_names(system_metrics))
407
+ elif args.command == "get":
408
+ if args.get_type == "project":
409
+ db_path = SQLiteStorage.get_project_db_path(args.project)
410
+ if not db_path.exists():
411
+ error_exit(f"Project '{args.project}' not found.")
412
+ summary = get_project_summary(args.project)
413
+ if args.json:
414
+ print(format_json(summary))
415
+ else:
416
+ print(format_project_summary(summary))
417
+ elif args.get_type == "run":
418
+ db_path = SQLiteStorage.get_project_db_path(args.project)
419
+ if not db_path.exists():
420
+ error_exit(f"Project '{args.project}' not found.")
421
+ runs = SQLiteStorage.get_runs(args.project)
422
+ if args.run not in runs:
423
+ error_exit(f"Run '{args.run}' not found in project '{args.project}'.")
424
+ summary = get_run_summary(args.project, args.run)
425
+ if args.json:
426
+ print(format_json(summary))
427
+ else:
428
+ print(format_run_summary(summary))
429
+ elif args.get_type == "metric":
430
+ db_path = SQLiteStorage.get_project_db_path(args.project)
431
+ if not db_path.exists():
432
+ error_exit(f"Project '{args.project}' not found.")
433
+ runs = SQLiteStorage.get_runs(args.project)
434
+ if args.run not in runs:
435
+ error_exit(f"Run '{args.run}' not found in project '{args.project}'.")
436
+ metrics = SQLiteStorage.get_all_metrics_for_run(args.project, args.run)
437
+ if args.metric not in metrics:
438
+ error_exit(
439
+ f"Metric '{args.metric}' not found in run '{args.run}' of project '{args.project}'."
440
+ )
441
+ values = SQLiteStorage.get_metric_values(
442
+ args.project, args.run, args.metric
443
+ )
444
+ if args.json:
445
+ print(
446
+ format_json(
447
+ {
448
+ "project": args.project,
449
+ "run": args.run,
450
+ "metric": args.metric,
451
+ "values": values,
452
+ }
453
+ )
454
+ )
455
+ else:
456
+ print(format_metric_values(values))
457
+ elif args.get_type == "system-metric":
458
+ db_path = SQLiteStorage.get_project_db_path(args.project)
459
+ if not db_path.exists():
460
+ error_exit(f"Project '{args.project}' not found.")
461
+ runs = SQLiteStorage.get_runs(args.project)
462
+ if args.run not in runs:
463
+ error_exit(f"Run '{args.run}' not found in project '{args.project}'.")
464
+ if args.metric:
465
+ system_metrics = SQLiteStorage.get_system_logs(args.project, args.run)
466
+ all_system_metric_names = SQLiteStorage.get_all_system_metrics_for_run(
467
+ args.project, args.run
468
+ )
469
+ if args.metric not in all_system_metric_names:
470
+ error_exit(
471
+ f"System metric '{args.metric}' not found in run '{args.run}' of project '{args.project}'."
472
+ )
473
+ filtered_metrics = [
474
+ {
475
+ k: v
476
+ for k, v in entry.items()
477
+ if k == "timestamp" or k == args.metric
478
+ }
479
+ for entry in system_metrics
480
+ if args.metric in entry
481
+ ]
482
+ if args.json:
483
+ print(
484
+ format_json(
485
+ {
486
+ "project": args.project,
487
+ "run": args.run,
488
+ "metric": args.metric,
489
+ "values": filtered_metrics,
490
+ }
491
+ )
492
+ )
493
+ else:
494
+ print(format_system_metrics(filtered_metrics))
495
+ else:
496
+ system_metrics = SQLiteStorage.get_system_logs(args.project, args.run)
497
+ if args.json:
498
+ print(
499
+ format_json(
500
+ {
501
+ "project": args.project,
502
+ "run": args.run,
503
+ "system_metrics": system_metrics,
504
+ }
505
+ )
506
+ )
507
+ else:
508
+ print(format_system_metrics(system_metrics))
509
+ else:
510
+ parser.print_help()
511
+
512
+
513
+ if __name__ == "__main__":
514
+ main()
trackio/cli_helpers.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ from typing import Any
4
+
5
+
6
+ def format_json(data: Any) -> str:
7
+ """Format data as JSON."""
8
+ return json.dumps(data, indent=2)
9
+
10
+
11
+ def format_list(items: list[str], title: str | None = None) -> str:
12
+ """Format a list of items in human-readable format."""
13
+ if not items:
14
+ return f"No {title.lower() if title else 'items'} found."
15
+
16
+ output = []
17
+ if title:
18
+ output.append(f"{title}:")
19
+
20
+ for item in items:
21
+ output.append(f" - {item}")
22
+
23
+ return "\n".join(output)
24
+
25
+
26
+ def format_project_summary(summary: dict) -> str:
27
+ """Format project summary in human-readable format."""
28
+ output = [f"Project: {summary['project']}"]
29
+ output.append(f"Number of runs: {summary['num_runs']}")
30
+
31
+ if summary["runs"]:
32
+ output.append("\nRuns:")
33
+ for run in summary["runs"]:
34
+ output.append(f" - {run}")
35
+ else:
36
+ output.append("\nNo runs found.")
37
+
38
+ if summary.get("last_activity"):
39
+ output.append(f"\nLast activity (max step): {summary['last_activity']}")
40
+
41
+ return "\n".join(output)
42
+
43
+
44
+ def format_run_summary(summary: dict) -> str:
45
+ """Format run summary in human-readable format."""
46
+ output = [f"Project: {summary['project']}"]
47
+ output.append(f"Run: {summary['run']}")
48
+ output.append(f"Number of logs: {summary['num_logs']}")
49
+
50
+ if summary.get("last_step") is not None:
51
+ output.append(f"Last step: {summary['last_step']}")
52
+
53
+ if summary.get("metrics"):
54
+ output.append("\nMetrics:")
55
+ for metric in summary["metrics"]:
56
+ output.append(f" - {metric}")
57
+ else:
58
+ output.append("\nNo metrics found.")
59
+
60
+ config = summary.get("config")
61
+ if config:
62
+ output.append("\nConfig:")
63
+ config_display = {k: v for k, v in config.items() if not k.startswith("_")}
64
+ if config_display:
65
+ for key, value in config_display.items():
66
+ output.append(f" {key}: {value}")
67
+ else:
68
+ output.append(" (no config)")
69
+ else:
70
+ output.append("\nConfig: (no config)")
71
+
72
+ return "\n".join(output)
73
+
74
+
75
+ def format_metric_values(values: list[dict]) -> str:
76
+ """Format metric values in human-readable format."""
77
+ if not values:
78
+ return "No metric values found."
79
+
80
+ output = [f"Found {len(values)} value(s):\n"]
81
+ output.append("Step | Timestamp | Value")
82
+ output.append("-" * 50)
83
+
84
+ for value in values:
85
+ step = value.get("step", "N/A")
86
+ timestamp = value.get("timestamp", "N/A")
87
+ val = value.get("value", "N/A")
88
+ output.append(f"{step} | {timestamp} | {val}")
89
+
90
+ return "\n".join(output)
91
+
92
+
93
+ def format_system_metrics(metrics: list[dict]) -> str:
94
+ """Format system metrics in human-readable format."""
95
+ if not metrics:
96
+ return "No system metrics found."
97
+
98
+ output = [f"Found {len(metrics)} system metric entry/entries:\n"]
99
+
100
+ for i, entry in enumerate(metrics):
101
+ timestamp = entry.get("timestamp", "N/A")
102
+ output.append(f"\nEntry {i + 1} (Timestamp: {timestamp}):")
103
+ for key, value in entry.items():
104
+ if key != "timestamp":
105
+ output.append(f" {key}: {value}")
106
+
107
+ return "\n".join(output)
108
+
109
+
110
+ def format_system_metric_names(names: list[str]) -> str:
111
+ """Format system metric names in human-readable format."""
112
+ return format_list(names, "System Metrics")
113
+
114
+
115
+ def error_exit(message: str, code: int = 1) -> None:
116
+ """Print error message and exit."""
117
+ print(f"Error: {message}", file=sys.stderr)
118
+ sys.exit(code)
trackio/commit_scheduler.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Originally copied from https://github.com/huggingface/huggingface_hub/blob/d0a948fc2a32ed6e557042a95ef3e4af97ec4a7c/src/huggingface_hub/_commit_scheduler.py
2
+
3
+ import atexit
4
+ import logging
5
+ import time
6
+ from concurrent.futures import Future
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from threading import Lock, Thread
10
+ from typing import Callable, Dict, List, Union
11
+
12
+ from huggingface_hub.hf_api import (
13
+ DEFAULT_IGNORE_PATTERNS,
14
+ CommitInfo,
15
+ CommitOperationAdd,
16
+ HfApi,
17
+ )
18
+ from huggingface_hub.utils import filter_repo_objects
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class _FileToUpload:
25
+ """Temporary dataclass to store info about files to upload. Not meant to be used directly."""
26
+
27
+ local_path: Path
28
+ path_in_repo: str
29
+ size_limit: int
30
+ last_modified: float
31
+
32
+
33
+ class CommitScheduler:
34
+ """
35
+ Scheduler to upload a local folder to the Hub at regular intervals (e.g. push to hub every 5 minutes).
36
+
37
+ The recommended way to use the scheduler is to use it as a context manager. This ensures that the scheduler is
38
+ properly stopped and the last commit is triggered when the script ends. The scheduler can also be stopped manually
39
+ with the `stop` method. Checkout the [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#scheduled-uploads)
40
+ to learn more about how to use it.
41
+
42
+ Args:
43
+ repo_id (`str`):
44
+ The id of the repo to commit to.
45
+ folder_path (`str` or `Path`):
46
+ Path to the local folder to upload regularly.
47
+ every (`int` or `float`, *optional*):
48
+ The number of minutes between each commit. Defaults to 5 minutes.
49
+ path_in_repo (`str`, *optional*):
50
+ Relative path of the directory in the repo, for example: `"checkpoints/"`. Defaults to the root folder
51
+ of the repository.
52
+ repo_type (`str`, *optional*):
53
+ The type of the repo to commit to. Defaults to `model`.
54
+ revision (`str`, *optional*):
55
+ The revision of the repo to commit to. Defaults to `main`.
56
+ private (`bool`, *optional*):
57
+ Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
58
+ token (`str`, *optional*):
59
+ The token to use to commit to the repo. Defaults to the token saved on the machine.
60
+ allow_patterns (`List[str]` or `str`, *optional*):
61
+ If provided, only files matching at least one pattern are uploaded.
62
+ ignore_patterns (`List[str]` or `str`, *optional*):
63
+ If provided, files matching any of the patterns are not uploaded.
64
+ squash_history (`bool`, *optional*):
65
+ Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
66
+ useful to avoid degraded performances on the repo when it grows too large.
67
+ hf_api (`HfApi`, *optional*):
68
+ The [`HfApi`] client to use to commit to the Hub. Can be set with custom settings (user agent, token,...).
69
+ on_before_commit (`Callable[[], None]`, *optional*):
70
+ If specified, a function that will be called before the CommitScheduler lists files to create a commit.
71
+
72
+ Example:
73
+ ```py
74
+ >>> from pathlib import Path
75
+ >>> from huggingface_hub import CommitScheduler
76
+
77
+ # Scheduler uploads every 10 minutes
78
+ >>> csv_path = Path("watched_folder/data.csv")
79
+ >>> CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path=csv_path.parent, every=10)
80
+
81
+ >>> with csv_path.open("a") as f:
82
+ ... f.write("first line")
83
+
84
+ # Some time later (...)
85
+ >>> with csv_path.open("a") as f:
86
+ ... f.write("second line")
87
+ ```
88
+
89
+ Example using a context manager:
90
+ ```py
91
+ >>> from pathlib import Path
92
+ >>> from huggingface_hub import CommitScheduler
93
+
94
+ >>> with CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path="watched_folder", every=10) as scheduler:
95
+ ... csv_path = Path("watched_folder/data.csv")
96
+ ... with csv_path.open("a") as f:
97
+ ... f.write("first line")
98
+ ... (...)
99
+ ... with csv_path.open("a") as f:
100
+ ... f.write("second line")
101
+
102
+ # Scheduler is now stopped and last commit have been triggered
103
+ ```
104
+ """
105
+
106
+ def __init__(
107
+ self,
108
+ *,
109
+ repo_id: str,
110
+ folder_path: Union[str, Path],
111
+ every: Union[int, float] = 5,
112
+ path_in_repo: str | None = None,
113
+ repo_type: str | None = None,
114
+ revision: str | None = None,
115
+ private: bool | None = None,
116
+ token: str | None = None,
117
+ allow_patterns: list[str] | str | None = None,
118
+ ignore_patterns: list[str] | str | None = None,
119
+ squash_history: bool = False,
120
+ hf_api: HfApi | None = None,
121
+ on_before_commit: Callable[[], None] | None = None,
122
+ ) -> None:
123
+ self.api = hf_api or HfApi(token=token)
124
+ self.on_before_commit = on_before_commit
125
+
126
+ # Folder
127
+ self.folder_path = Path(folder_path).expanduser().resolve()
128
+ self.path_in_repo = path_in_repo or ""
129
+ self.allow_patterns = allow_patterns
130
+
131
+ if ignore_patterns is None:
132
+ ignore_patterns = []
133
+ elif isinstance(ignore_patterns, str):
134
+ ignore_patterns = [ignore_patterns]
135
+ self.ignore_patterns = ignore_patterns + DEFAULT_IGNORE_PATTERNS
136
+
137
+ if self.folder_path.is_file():
138
+ raise ValueError(
139
+ f"'folder_path' must be a directory, not a file: '{self.folder_path}'."
140
+ )
141
+ self.folder_path.mkdir(parents=True, exist_ok=True)
142
+
143
+ # Repository
144
+ repo_url = self.api.create_repo(
145
+ repo_id=repo_id, private=private, repo_type=repo_type, exist_ok=True
146
+ )
147
+ self.repo_id = repo_url.repo_id
148
+ self.repo_type = repo_type
149
+ self.revision = revision
150
+ self.token = token
151
+
152
+ self.last_uploaded: Dict[Path, float] = {}
153
+ self.last_push_time: float | None = None
154
+
155
+ if not every > 0:
156
+ raise ValueError(f"'every' must be a positive integer, not '{every}'.")
157
+ self.lock = Lock()
158
+ self.every = every
159
+ self.squash_history = squash_history
160
+
161
+ logger.info(
162
+ f"Scheduled job to push '{self.folder_path}' to '{self.repo_id}' every {self.every} minutes."
163
+ )
164
+ self._scheduler_thread = Thread(target=self._run_scheduler, daemon=True)
165
+ self._scheduler_thread.start()
166
+ atexit.register(self._push_to_hub)
167
+
168
+ self.__stopped = False
169
+
170
+ def stop(self) -> None:
171
+ """Stop the scheduler.
172
+
173
+ A stopped scheduler cannot be restarted. Mostly for tests purposes.
174
+ """
175
+ self.__stopped = True
176
+
177
+ def __enter__(self) -> "CommitScheduler":
178
+ return self
179
+
180
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
181
+ # Upload last changes before exiting
182
+ self.trigger().result()
183
+ self.stop()
184
+ return
185
+
186
+ def _run_scheduler(self) -> None:
187
+ """Dumb thread waiting between each scheduled push to Hub."""
188
+ while True:
189
+ self.last_future = self.trigger()
190
+ time.sleep(self.every * 60)
191
+ if self.__stopped:
192
+ break
193
+
194
+ def trigger(self) -> Future:
195
+ """Trigger a `push_to_hub` and return a future.
196
+
197
+ This method is automatically called every `every` minutes. You can also call it manually to trigger a commit
198
+ immediately, without waiting for the next scheduled commit.
199
+ """
200
+ return self.api.run_as_future(self._push_to_hub)
201
+
202
+ def _push_to_hub(self) -> CommitInfo | None:
203
+ if self.__stopped: # If stopped, already scheduled commits are ignored
204
+ return None
205
+
206
+ logger.info("(Background) scheduled commit triggered.")
207
+ try:
208
+ value = self.push_to_hub()
209
+ if self.squash_history:
210
+ logger.info("(Background) squashing repo history.")
211
+ self.api.super_squash_history(
212
+ repo_id=self.repo_id, repo_type=self.repo_type, branch=self.revision
213
+ )
214
+ return value
215
+ except Exception as e:
216
+ logger.error(
217
+ f"Error while pushing to Hub: {e}"
218
+ ) # Depending on the setup, error might be silenced
219
+ raise
220
+
221
+ def push_to_hub(self) -> CommitInfo | None:
222
+ """
223
+ Push folder to the Hub and return the commit info.
224
+
225
+ <Tip warning={true}>
226
+
227
+ This method is not meant to be called directly. It is run in the background by the scheduler, respecting a
228
+ queue mechanism to avoid concurrent commits. Making a direct call to the method might lead to concurrency
229
+ issues.
230
+
231
+ </Tip>
232
+
233
+ The default behavior of `push_to_hub` is to assume an append-only folder. It lists all files in the folder and
234
+ uploads only changed files. If no changes are found, the method returns without committing anything. If you want
235
+ to change this behavior, you can inherit from [`CommitScheduler`] and override this method. This can be useful
236
+ for example to compress data together in a single file before committing. For more details and examples, check
237
+ out our [integration guide](https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#scheduled-uploads).
238
+ """
239
+ # Check files to upload (with lock)
240
+ with self.lock:
241
+ if self.on_before_commit is not None:
242
+ self.on_before_commit()
243
+
244
+ logger.debug("Listing files to upload for scheduled commit.")
245
+
246
+ # List files from folder (taken from `_prepare_upload_folder_additions`)
247
+ relpath_to_abspath = {
248
+ path.relative_to(self.folder_path).as_posix(): path
249
+ for path in sorted(
250
+ self.folder_path.glob("**/*")
251
+ ) # sorted to be deterministic
252
+ if path.is_file()
253
+ }
254
+ prefix = f"{self.path_in_repo.strip('/')}/" if self.path_in_repo else ""
255
+
256
+ # Filter with pattern + filter out unchanged files + retrieve current file size
257
+ files_to_upload: List[_FileToUpload] = []
258
+ for relpath in filter_repo_objects(
259
+ relpath_to_abspath.keys(),
260
+ allow_patterns=self.allow_patterns,
261
+ ignore_patterns=self.ignore_patterns,
262
+ ):
263
+ local_path = relpath_to_abspath[relpath]
264
+ stat = local_path.stat()
265
+ if (
266
+ self.last_uploaded.get(local_path) is None
267
+ or self.last_uploaded[local_path] != stat.st_mtime
268
+ ):
269
+ files_to_upload.append(
270
+ _FileToUpload(
271
+ local_path=local_path,
272
+ path_in_repo=prefix + relpath,
273
+ size_limit=stat.st_size,
274
+ last_modified=stat.st_mtime,
275
+ )
276
+ )
277
+
278
+ # Return if nothing to upload
279
+ if len(files_to_upload) == 0:
280
+ logger.debug("Dropping schedule commit: no changed file to upload.")
281
+ return None
282
+
283
+ # Convert `_FileToUpload` as `CommitOperationAdd` (=> compute file shas + limit to file size)
284
+ logger.debug("Removing unchanged files since previous scheduled commit.")
285
+ add_operations = [
286
+ CommitOperationAdd(
287
+ # TODO: Cap the file to its current size, even if the user append data to it while a scheduled commit is happening
288
+ # (requires an upstream fix for XET-535: `hf_xet` should support `BinaryIO` for upload)
289
+ path_or_fileobj=file_to_upload.local_path,
290
+ path_in_repo=file_to_upload.path_in_repo,
291
+ )
292
+ for file_to_upload in files_to_upload
293
+ ]
294
+
295
+ # Upload files (append mode expected - no need for lock)
296
+ logger.debug("Uploading files for scheduled commit.")
297
+ commit_info = self.api.create_commit(
298
+ repo_id=self.repo_id,
299
+ repo_type=self.repo_type,
300
+ operations=add_operations,
301
+ commit_message="Scheduled Commit",
302
+ revision=self.revision,
303
+ )
304
+
305
+ for file in files_to_upload:
306
+ self.last_uploaded[file.local_path] = file.last_modified
307
+
308
+ self.last_push_time = time.time()
309
+
310
+ return commit_info
trackio/context_vars.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import contextvars
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from trackio.run import Run
6
+
7
+ current_run: contextvars.ContextVar["Run | None"] = contextvars.ContextVar(
8
+ "current_run", default=None
9
+ )
10
+ current_project: contextvars.ContextVar[str | None] = contextvars.ContextVar(
11
+ "current_project", default=None
12
+ )
13
+ current_server: contextvars.ContextVar[str | None] = contextvars.ContextVar(
14
+ "current_server", default=None
15
+ )
16
+ current_space_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
17
+ "current_space_id", default=None
18
+ )
trackio/deploy.py ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.metadata
2
+ import io
3
+ import os
4
+ import sys
5
+ import threading
6
+ import time
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+
10
+ if sys.version_info >= (3, 11):
11
+ import tomllib
12
+ else:
13
+ import tomli as tomllib
14
+
15
+ import gradio
16
+ import huggingface_hub
17
+ from gradio_client import Client, handle_file
18
+ from httpx import ReadTimeout
19
+ from huggingface_hub.errors import HfHubHTTPError, RepositoryNotFoundError
20
+
21
+ import trackio
22
+ from trackio.sqlite_storage import SQLiteStorage
23
+ from trackio.utils import get_or_create_project_hash, preprocess_space_and_dataset_ids
24
+
25
+ SPACE_HOST_URL = "https://{user_name}-{space_name}.hf.space/"
26
+ SPACE_URL = "https://huggingface.co/spaces/{space_id}"
27
+
28
+
29
+ def _get_source_install_dependencies() -> str:
30
+ """Get trackio dependencies from pyproject.toml for source installs."""
31
+ trackio_path = files("trackio")
32
+ pyproject_path = Path(trackio_path).parent / "pyproject.toml"
33
+ with open(pyproject_path, "rb") as f:
34
+ pyproject = tomllib.load(f)
35
+ deps = pyproject["project"]["dependencies"]
36
+ spaces_deps = (
37
+ pyproject["project"].get("optional-dependencies", {}).get("spaces", [])
38
+ )
39
+ return "\n".join(deps + spaces_deps)
40
+
41
+
42
+ def _is_trackio_installed_from_source() -> bool:
43
+ """Check if trackio is installed from source/editable install vs PyPI."""
44
+ try:
45
+ trackio_file = trackio.__file__
46
+ if "site-packages" not in trackio_file and "dist-packages" not in trackio_file:
47
+ return True
48
+
49
+ dist = importlib.metadata.distribution("trackio")
50
+ if dist.files:
51
+ files = list(dist.files)
52
+ has_pth = any(".pth" in str(f) for f in files)
53
+ if has_pth:
54
+ return True
55
+
56
+ return False
57
+ except (
58
+ AttributeError,
59
+ importlib.metadata.PackageNotFoundError,
60
+ importlib.metadata.MetadataError,
61
+ ValueError,
62
+ TypeError,
63
+ ):
64
+ return True
65
+
66
+
67
+ def deploy_as_space(
68
+ space_id: str,
69
+ space_storage: huggingface_hub.SpaceStorage | None = None,
70
+ dataset_id: str | None = None,
71
+ private: bool | None = None,
72
+ ):
73
+ if (
74
+ os.getenv("SYSTEM") == "spaces"
75
+ ): # in case a repo with this function is uploaded to spaces
76
+ return
77
+
78
+ trackio_path = files("trackio")
79
+
80
+ hf_api = huggingface_hub.HfApi()
81
+
82
+ try:
83
+ huggingface_hub.create_repo(
84
+ space_id,
85
+ private=private,
86
+ space_sdk="gradio",
87
+ space_storage=space_storage,
88
+ repo_type="space",
89
+ exist_ok=True,
90
+ )
91
+ except HfHubHTTPError as e:
92
+ if e.response.status_code in [401, 403]: # unauthorized or forbidden
93
+ print("Need 'write' access token to create a Spaces repo.")
94
+ huggingface_hub.login(add_to_git_credential=False)
95
+ huggingface_hub.create_repo(
96
+ space_id,
97
+ private=private,
98
+ space_sdk="gradio",
99
+ space_storage=space_storage,
100
+ repo_type="space",
101
+ exist_ok=True,
102
+ )
103
+ else:
104
+ raise ValueError(f"Failed to create Space: {e}")
105
+
106
+ # We can assume pandas, gradio, and huggingface-hub are already installed in a Gradio Space.
107
+ # Make sure necessary dependencies are installed by creating a requirements.txt.
108
+ is_source_install = _is_trackio_installed_from_source()
109
+
110
+ with open(Path(trackio_path, "README.md"), "r") as f:
111
+ readme_content = f.read()
112
+ readme_content = readme_content.replace("{GRADIO_VERSION}", gradio.__version__)
113
+ if is_source_install:
114
+ readme_content = readme_content.replace("{APP_FILE}", "trackio/ui/main.py")
115
+ else:
116
+ readme_content = readme_content.replace("{APP_FILE}", "app.py")
117
+ readme_buffer = io.BytesIO(readme_content.encode("utf-8"))
118
+ hf_api.upload_file(
119
+ path_or_fileobj=readme_buffer,
120
+ path_in_repo="README.md",
121
+ repo_id=space_id,
122
+ repo_type="space",
123
+ )
124
+
125
+ if is_source_install:
126
+ requirements_content = _get_source_install_dependencies()
127
+ else:
128
+ requirements_content = f"trackio[spaces]=={trackio.__version__}"
129
+
130
+ requirements_buffer = io.BytesIO(requirements_content.encode("utf-8"))
131
+ hf_api.upload_file(
132
+ path_or_fileobj=requirements_buffer,
133
+ path_in_repo="requirements.txt",
134
+ repo_id=space_id,
135
+ repo_type="space",
136
+ )
137
+
138
+ huggingface_hub.utils.disable_progress_bars()
139
+
140
+ if is_source_install:
141
+ hf_api.upload_folder(
142
+ repo_id=space_id,
143
+ repo_type="space",
144
+ folder_path=trackio_path,
145
+ path_in_repo="trackio",
146
+ ignore_patterns=["README.md"],
147
+ )
148
+
149
+ app_file_content = """import trackio
150
+ trackio.show()"""
151
+ app_file_buffer = io.BytesIO(app_file_content.encode("utf-8"))
152
+ hf_api.upload_file(
153
+ path_or_fileobj=app_file_buffer,
154
+ path_in_repo="app.py",
155
+ repo_id=space_id,
156
+ repo_type="space",
157
+ )
158
+
159
+ if hf_token := huggingface_hub.utils.get_token():
160
+ huggingface_hub.add_space_secret(space_id, "HF_TOKEN", hf_token)
161
+ if dataset_id is not None:
162
+ huggingface_hub.add_space_variable(space_id, "TRACKIO_DATASET_ID", dataset_id)
163
+ if logo_light_url := os.environ.get("TRACKIO_LOGO_LIGHT_URL"):
164
+ huggingface_hub.add_space_variable(
165
+ space_id, "TRACKIO_LOGO_LIGHT_URL", logo_light_url
166
+ )
167
+ if logo_dark_url := os.environ.get("TRACKIO_LOGO_DARK_URL"):
168
+ huggingface_hub.add_space_variable(
169
+ space_id, "TRACKIO_LOGO_DARK_URL", logo_dark_url
170
+ )
171
+ if plot_order := os.environ.get("TRACKIO_PLOT_ORDER"):
172
+ huggingface_hub.add_space_variable(space_id, "TRACKIO_PLOT_ORDER", plot_order)
173
+ if theme := os.environ.get("TRACKIO_THEME"):
174
+ huggingface_hub.add_space_variable(space_id, "TRACKIO_THEME", theme)
175
+ huggingface_hub.add_space_variable(space_id, "GRADIO_MCP_SERVER", "True")
176
+
177
+
178
+ def create_space_if_not_exists(
179
+ space_id: str,
180
+ space_storage: huggingface_hub.SpaceStorage | None = None,
181
+ dataset_id: str | None = None,
182
+ private: bool | None = None,
183
+ ) -> None:
184
+ """
185
+ Creates a new Hugging Face Space if it does not exist.
186
+
187
+ Args:
188
+ space_id (`str`):
189
+ The ID of the Space to create.
190
+ space_storage ([`~huggingface_hub.SpaceStorage`], *optional*):
191
+ Choice of persistent storage tier for the Space.
192
+ dataset_id (`str`, *optional*):
193
+ The ID of the Dataset to add to the Space as a space variable.
194
+ private (`bool`, *optional*):
195
+ Whether to make the Space private. If `None` (default), the repo will be
196
+ public unless the organization's default is private. This value is ignored
197
+ if the repo already exists.
198
+ """
199
+ if "/" not in space_id:
200
+ raise ValueError(
201
+ f"Invalid space ID: {space_id}. Must be in the format: username/reponame or orgname/reponame."
202
+ )
203
+ if dataset_id is not None and "/" not in dataset_id:
204
+ raise ValueError(
205
+ f"Invalid dataset ID: {dataset_id}. Must be in the format: username/datasetname or orgname/datasetname."
206
+ )
207
+ try:
208
+ huggingface_hub.repo_info(space_id, repo_type="space")
209
+ print(f"* Found existing space: {SPACE_URL.format(space_id=space_id)}")
210
+ return
211
+ except RepositoryNotFoundError:
212
+ pass
213
+ except HfHubHTTPError as e:
214
+ if e.response.status_code in [401, 403]: # unauthorized or forbidden
215
+ print("Need 'write' access token to create a Spaces repo.")
216
+ huggingface_hub.login(add_to_git_credential=False)
217
+ else:
218
+ raise ValueError(f"Failed to create Space: {e}")
219
+
220
+ print(f"* Creating new space: {SPACE_URL.format(space_id=space_id)}")
221
+ deploy_as_space(space_id, space_storage, dataset_id, private)
222
+
223
+
224
+ def wait_until_space_exists(
225
+ space_id: str,
226
+ ) -> None:
227
+ """
228
+ Blocks the current thread until the Space exists.
229
+
230
+ Args:
231
+ space_id (`str`):
232
+ The ID of the Space to wait for.
233
+
234
+ Raises:
235
+ `TimeoutError`: If waiting for the Space takes longer than expected.
236
+ """
237
+ hf_api = huggingface_hub.HfApi()
238
+ delay = 1
239
+ for _ in range(30):
240
+ try:
241
+ hf_api.space_info(space_id)
242
+ return
243
+ except (huggingface_hub.utils.HfHubHTTPError, ReadTimeout):
244
+ time.sleep(delay)
245
+ delay = min(delay * 2, 60)
246
+ raise TimeoutError("Waiting for space to exist took longer than expected")
247
+
248
+
249
+ def upload_db_to_space(project: str, space_id: str, force: bool = False) -> None:
250
+ """
251
+ Uploads the database of a local Trackio project to a Hugging Face Space.
252
+
253
+ This uses the Gradio Client to upload since we do not want to trigger a new build of
254
+ the Space, which would happen if we used `huggingface_hub.upload_file`.
255
+
256
+ Args:
257
+ project (`str`):
258
+ The name of the project to upload.
259
+ space_id (`str`):
260
+ The ID of the Space to upload to.
261
+ force (`bool`, *optional*, defaults to `False`):
262
+ If `True`, overwrites the existing database without prompting. If `False`,
263
+ prompts for confirmation.
264
+ """
265
+ db_path = SQLiteStorage.get_project_db_path(project)
266
+ client = Client(space_id, verbose=False, httpx_kwargs={"timeout": 90})
267
+
268
+ if not force:
269
+ try:
270
+ existing_projects = client.predict(api_name="/get_all_projects")
271
+ if project in existing_projects:
272
+ response = input(
273
+ f"Database for project '{project}' already exists on Space '{space_id}'. "
274
+ f"Overwrite it? (y/N): "
275
+ )
276
+ if response.lower() not in ["y", "yes"]:
277
+ print("* Upload cancelled.")
278
+ return
279
+ except Exception as e:
280
+ print(f"* Warning: Could not check if project exists on Space: {e}")
281
+ print("* Proceeding with upload...")
282
+
283
+ client.predict(
284
+ api_name="/upload_db_to_space",
285
+ project=project,
286
+ uploaded_db=handle_file(db_path),
287
+ hf_token=huggingface_hub.utils.get_token(),
288
+ )
289
+
290
+
291
+ SYNC_BATCH_SIZE = 500
292
+
293
+
294
+ def sync_incremental(
295
+ project: str,
296
+ space_id: str,
297
+ private: bool | None = None,
298
+ pending_only: bool = False,
299
+ ) -> None:
300
+ """
301
+ Syncs a local Trackio project to a Space via the bulk_log API endpoints
302
+ instead of uploading the entire DB file. Supports incremental sync.
303
+
304
+ Args:
305
+ project: The name of the project to sync.
306
+ space_id: The HF Space ID to sync to.
307
+ private: Whether to make the Space private if creating.
308
+ pending_only: If True, only sync rows tagged with space_id (pending data).
309
+ """
310
+ print(
311
+ f"* Syncing project '{project}' to: {SPACE_URL.format(space_id=space_id)} (please wait...)"
312
+ )
313
+ create_space_if_not_exists(space_id, private=private)
314
+ wait_until_space_exists(space_id)
315
+
316
+ client = Client(space_id, verbose=False, httpx_kwargs={"timeout": 90})
317
+ hf_token = huggingface_hub.utils.get_token()
318
+
319
+ if pending_only:
320
+ pending_logs = SQLiteStorage.get_pending_logs(project)
321
+ if pending_logs:
322
+ logs = pending_logs["logs"]
323
+ for i in range(0, len(logs), SYNC_BATCH_SIZE):
324
+ batch = logs[i : i + SYNC_BATCH_SIZE]
325
+ print(
326
+ f" Syncing metrics: {min(i + SYNC_BATCH_SIZE, len(logs))}/{len(logs)}..."
327
+ )
328
+ client.predict(api_name="/bulk_log", logs=batch, hf_token=hf_token)
329
+ SQLiteStorage.clear_pending_logs(project, pending_logs["ids"])
330
+
331
+ pending_sys = SQLiteStorage.get_pending_system_logs(project)
332
+ if pending_sys:
333
+ logs = pending_sys["logs"]
334
+ for i in range(0, len(logs), SYNC_BATCH_SIZE):
335
+ batch = logs[i : i + SYNC_BATCH_SIZE]
336
+ print(
337
+ f" Syncing system metrics: {min(i + SYNC_BATCH_SIZE, len(logs))}/{len(logs)}..."
338
+ )
339
+ client.predict(
340
+ api_name="/bulk_log_system", logs=batch, hf_token=hf_token
341
+ )
342
+ SQLiteStorage.clear_pending_system_logs(project, pending_sys["ids"])
343
+
344
+ pending_uploads = SQLiteStorage.get_pending_uploads(project)
345
+ if pending_uploads:
346
+ upload_entries = []
347
+ for u in pending_uploads["uploads"]:
348
+ fp = u["file_path"]
349
+ if os.path.exists(fp):
350
+ upload_entries.append(
351
+ {
352
+ "project": u["project"],
353
+ "run": u["run"],
354
+ "step": u["step"],
355
+ "relative_path": u["relative_path"],
356
+ "uploaded_file": handle_file(fp),
357
+ }
358
+ )
359
+ if upload_entries:
360
+ print(f" Syncing {len(upload_entries)} media files...")
361
+ client.predict(
362
+ api_name="/bulk_upload_media",
363
+ uploads=upload_entries,
364
+ hf_token=hf_token,
365
+ )
366
+ SQLiteStorage.clear_pending_uploads(project, pending_uploads["ids"])
367
+ else:
368
+ all_logs = SQLiteStorage.get_all_logs_for_sync(project)
369
+ if all_logs:
370
+ for i in range(0, len(all_logs), SYNC_BATCH_SIZE):
371
+ batch = all_logs[i : i + SYNC_BATCH_SIZE]
372
+ print(
373
+ f" Syncing metrics: {min(i + SYNC_BATCH_SIZE, len(all_logs))}/{len(all_logs)}..."
374
+ )
375
+ client.predict(api_name="/bulk_log", logs=batch, hf_token=hf_token)
376
+
377
+ all_sys_logs = SQLiteStorage.get_all_system_logs_for_sync(project)
378
+ if all_sys_logs:
379
+ for i in range(0, len(all_sys_logs), SYNC_BATCH_SIZE):
380
+ batch = all_sys_logs[i : i + SYNC_BATCH_SIZE]
381
+ print(
382
+ f" Syncing system metrics: {min(i + SYNC_BATCH_SIZE, len(all_sys_logs))}/{len(all_sys_logs)}..."
383
+ )
384
+ client.predict(
385
+ api_name="/bulk_log_system", logs=batch, hf_token=hf_token
386
+ )
387
+
388
+ SQLiteStorage.set_project_metadata(project, "space_id", space_id)
389
+ print(f"* Synced successfully to space: {SPACE_URL.format(space_id=space_id)}")
390
+
391
+
392
+ def sync(
393
+ project: str,
394
+ space_id: str | None = None,
395
+ private: bool | None = None,
396
+ force: bool = False,
397
+ run_in_background: bool = False,
398
+ ) -> str:
399
+ """
400
+ Syncs a local Trackio project's database to a Hugging Face Space.
401
+ If the Space does not exist, it will be created.
402
+
403
+ Args:
404
+ project (`str`): The name of the project to upload.
405
+ space_id (`str`, *optional*): The ID of the Space to upload to (e.g., `"username/space_id"`).
406
+ If not provided, checks project metadata first, then generates a random space_id.
407
+ private (`bool`, *optional*):
408
+ Whether to make the Space private. If None (default), the repo will be
409
+ public unless the organization's default is private. This value is ignored
410
+ if the repo already exists.
411
+ force (`bool`, *optional*, defaults to `False`):
412
+ If `True`, overwrite the existing database without prompting for confirmation.
413
+ If `False`, prompt the user before overwriting an existing database.
414
+ run_in_background (`bool`, *optional*, defaults to `False`):
415
+ If `True`, the Space creation and database upload will be run in a background thread.
416
+ If `False`, all the steps will be run synchronously.
417
+ Returns:
418
+ `str`: The Space ID of the synced project.
419
+ """
420
+ if space_id is None:
421
+ space_id = SQLiteStorage.get_space_id(project)
422
+ if space_id is None:
423
+ space_id = f"{project}-{get_or_create_project_hash(project)}"
424
+ space_id, _ = preprocess_space_and_dataset_ids(space_id, None)
425
+
426
+ def _do_sync(space_id: str, private: bool | None = None):
427
+ sync_incremental(project, space_id, private=private, pending_only=False)
428
+
429
+ if run_in_background:
430
+ threading.Thread(target=_do_sync, args=(space_id, private)).start()
431
+ else:
432
+ _do_sync(space_id, private)
433
+ return space_id