Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- trackio/CHANGELOG.md +160 -0
- trackio/__init__.py +664 -0
- trackio/__pycache__/__init__.cpython-312.pyc +0 -0
- trackio/__pycache__/__init__.cpython-313.pyc +0 -0
- trackio/__pycache__/alerts.cpython-313.pyc +0 -0
- trackio/__pycache__/api.cpython-312.pyc +0 -0
- trackio/__pycache__/api.cpython-313.pyc +0 -0
- trackio/__pycache__/cli.cpython-312.pyc +0 -0
- trackio/__pycache__/cli.cpython-313.pyc +0 -0
- trackio/__pycache__/cli_helpers.cpython-313.pyc +0 -0
- trackio/__pycache__/commit_scheduler.cpython-312.pyc +0 -0
- trackio/__pycache__/commit_scheduler.cpython-313.pyc +0 -0
- trackio/__pycache__/context_vars.cpython-312.pyc +0 -0
- trackio/__pycache__/context_vars.cpython-313.pyc +0 -0
- trackio/__pycache__/deploy.cpython-312.pyc +0 -0
- trackio/__pycache__/deploy.cpython-313.pyc +0 -0
- trackio/__pycache__/dummy_commit_scheduler.cpython-312.pyc +0 -0
- trackio/__pycache__/dummy_commit_scheduler.cpython-313.pyc +0 -0
- trackio/__pycache__/file_storage.cpython-312.pyc +0 -0
- trackio/__pycache__/gpu.cpython-312.pyc +0 -0
- trackio/__pycache__/gpu.cpython-313.pyc +0 -0
- trackio/__pycache__/histogram.cpython-312.pyc +0 -0
- trackio/__pycache__/histogram.cpython-313.pyc +0 -0
- trackio/__pycache__/imports.cpython-312.pyc +0 -0
- trackio/__pycache__/imports.cpython-313.pyc +0 -0
- trackio/__pycache__/markdown.cpython-313.pyc +0 -0
- trackio/__pycache__/media.cpython-312.pyc +0 -0
- trackio/__pycache__/run.cpython-312.pyc +0 -0
- trackio/__pycache__/run.cpython-313.pyc +0 -0
- trackio/__pycache__/sqlite_storage.cpython-312.pyc +0 -0
- trackio/__pycache__/sqlite_storage.cpython-313.pyc +0 -0
- trackio/__pycache__/table.cpython-312.pyc +0 -0
- trackio/__pycache__/table.cpython-313.pyc +0 -0
- trackio/__pycache__/typehints.cpython-312.pyc +0 -0
- trackio/__pycache__/typehints.cpython-313.pyc +0 -0
- trackio/__pycache__/ui.cpython-312.pyc +0 -0
- trackio/__pycache__/utils.cpython-312.pyc +0 -0
- trackio/__pycache__/utils.cpython-313.pyc +0 -0
- trackio/__pycache__/video_writer.cpython-312.pyc +0 -0
- trackio/alerts.py +185 -0
- trackio/api.py +87 -0
- trackio/assets/badge.png +0 -0
- trackio/assets/trackio_logo_dark.png +0 -0
- trackio/assets/trackio_logo_light.png +0 -0
- trackio/assets/trackio_logo_old.png +3 -0
- trackio/assets/trackio_logo_type_dark.png +0 -0
- trackio/assets/trackio_logo_type_dark_transparent.png +0 -0
- trackio/assets/trackio_logo_type_light.png +0 -0
- trackio/assets/trackio_logo_type_light_transparent.png +0 -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,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# trackio
|
| 2 |
+
|
| 3 |
+
## 0.17.0
|
| 4 |
+
|
| 5 |
+
### Features
|
| 6 |
+
|
| 7 |
+
- [#428](https://github.com/gradio-app/trackio/pull/428) [`f7dd1ce`](https://github.com/gradio-app/trackio/commit/f7dd1ce2dc8a1936f9983467fcbcf93bfef01e09) - feat: add ability to rename runs. Thanks @Saba9!
|
| 8 |
+
- [#437](https://github.com/gradio-app/trackio/pull/437) [`2727c0b`](https://github.com/gradio-app/trackio/commit/2727c0b0755f48f7f186162ea45185c98f6b5516) - Add markdown reports across Trackio. Thanks @abidlabs!
|
| 9 |
+
- [#427](https://github.com/gradio-app/trackio/pull/427) [`5aeb9ed`](https://github.com/gradio-app/trackio/commit/5aeb9edcfd2068d309d9d64f172dcbcc327be1ab) - Make Trackio logging much more robust. Thanks @abidlabs!
|
| 10 |
+
|
| 11 |
+
## 0.16.1
|
| 12 |
+
|
| 13 |
+
### Features
|
| 14 |
+
|
| 15 |
+
- [#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!
|
| 16 |
+
|
| 17 |
+
## 0.16.0
|
| 18 |
+
|
| 19 |
+
### Features
|
| 20 |
+
|
| 21 |
+
- [#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!
|
| 22 |
+
- [#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!
|
| 23 |
+
- [#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!
|
| 24 |
+
- [#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!
|
| 25 |
+
- [#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!
|
| 26 |
+
|
| 27 |
+
## 0.15.0
|
| 28 |
+
|
| 29 |
+
### Features
|
| 30 |
+
|
| 31 |
+
- [#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!
|
| 32 |
+
- [#396](https://github.com/gradio-app/trackio/pull/396) [`4a4d1ab`](https://github.com/gradio-app/trackio/commit/4a4d1ab85e63d923132a3fa7afa5d90e16431bec) - Fix run selection issue. Thanks @abidlabs!
|
| 33 |
+
- [#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!
|
| 34 |
+
- [#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!
|
| 35 |
+
|
| 36 |
+
## 0.14.2
|
| 37 |
+
|
| 38 |
+
### Features
|
| 39 |
+
|
| 40 |
+
- [#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!
|
| 41 |
+
|
| 42 |
+
## 0.14.1
|
| 43 |
+
|
| 44 |
+
### Features
|
| 45 |
+
|
| 46 |
+
- [#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!
|
| 47 |
+
- [#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!
|
| 48 |
+
|
| 49 |
+
## 0.14.0
|
| 50 |
+
|
| 51 |
+
### Features
|
| 52 |
+
|
| 53 |
+
- [#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!
|
| 54 |
+
- [#374](https://github.com/gradio-app/trackio/pull/374) [`388e26b`](https://github.com/gradio-app/trackio/commit/388e26b9e9f24cd7ad203affe9b709be885b3d24) - Save Optimized Parquet files. Thanks @lhoestq!
|
| 55 |
+
- [#371](https://github.com/gradio-app/trackio/pull/371) [`fbace9c`](https://github.com/gradio-app/trackio/commit/fbace9cd7732c166f34d268f54b05bb06846cc5d) - Add GPU metrics logging. Thanks @kashif!
|
| 56 |
+
- [#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!
|
| 57 |
+
|
| 58 |
+
## 0.13.1
|
| 59 |
+
|
| 60 |
+
### Features
|
| 61 |
+
|
| 62 |
+
- [#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!
|
| 63 |
+
|
| 64 |
+
## 0.13.0
|
| 65 |
+
|
| 66 |
+
### Features
|
| 67 |
+
|
| 68 |
+
- [#358](https://github.com/gradio-app/trackio/pull/358) [`073715d`](https://github.com/gradio-app/trackio/commit/073715d1caf8282f68890117f09c3ac301205312) - Improvements to `trackio.sync()`. Thanks @abidlabs!
|
| 69 |
+
|
| 70 |
+
## 0.12.0
|
| 71 |
+
|
| 72 |
+
### Features
|
| 73 |
+
|
| 74 |
+
- [#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!
|
| 75 |
+
- [#359](https://github.com/gradio-app/trackio/pull/359) [`08fe9c9`](https://github.com/gradio-app/trackio/commit/08fe9c9ddd7fe99ee811555fdfb62df9ab88e939) - docs: Improve docstrings. Thanks @qgallouedec!
|
| 76 |
+
|
| 77 |
+
## 0.11.0
|
| 78 |
+
|
| 79 |
+
### Features
|
| 80 |
+
|
| 81 |
+
- [#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!
|
| 82 |
+
- [#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!
|
| 83 |
+
- [#351](https://github.com/gradio-app/trackio/pull/351) [`8a8957e`](https://github.com/gradio-app/trackio/commit/8a8957e530dd7908d1fef7f2df030303f808101f) - Add `trackio.save()`. Thanks @abidlabs!
|
| 84 |
+
|
| 85 |
+
## 0.10.0
|
| 86 |
+
|
| 87 |
+
### Features
|
| 88 |
+
|
| 89 |
+
- [#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!
|
| 90 |
+
|
| 91 |
+
## 0.9.1
|
| 92 |
+
|
| 93 |
+
### Features
|
| 94 |
+
|
| 95 |
+
- [#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!
|
| 96 |
+
|
| 97 |
+
## 0.9.0
|
| 98 |
+
|
| 99 |
+
### Features
|
| 100 |
+
|
| 101 |
+
- [#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!
|
| 102 |
+
- [#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!
|
| 103 |
+
- [#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!
|
| 104 |
+
|
| 105 |
+
## 0.8.1
|
| 106 |
+
|
| 107 |
+
### Features
|
| 108 |
+
|
| 109 |
+
- [#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!
|
| 110 |
+
|
| 111 |
+
## 0.8.0
|
| 112 |
+
|
| 113 |
+
### Features
|
| 114 |
+
|
| 115 |
+
- [#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!
|
| 116 |
+
- [#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!
|
| 117 |
+
- [#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!
|
| 118 |
+
- [#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!
|
| 119 |
+
- [#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!
|
| 120 |
+
|
| 121 |
+
## 0.7.0
|
| 122 |
+
|
| 123 |
+
### Features
|
| 124 |
+
|
| 125 |
+
- [#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!
|
| 126 |
+
- [#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!
|
| 127 |
+
- [#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!
|
| 128 |
+
|
| 129 |
+
## 0.6.0
|
| 130 |
+
|
| 131 |
+
### Features
|
| 132 |
+
|
| 133 |
+
- [#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!
|
| 134 |
+
- [#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!
|
| 135 |
+
- [#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!
|
| 136 |
+
- [#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!
|
| 137 |
+
- [#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!
|
| 138 |
+
|
| 139 |
+
## 0.5.3
|
| 140 |
+
|
| 141 |
+
### Features
|
| 142 |
+
|
| 143 |
+
- [#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!
|
| 144 |
+
|
| 145 |
+
## 0.5.2
|
| 146 |
+
|
| 147 |
+
### Features
|
| 148 |
+
|
| 149 |
+
- [#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!
|
| 150 |
+
- [#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!
|
| 151 |
+
|
| 152 |
+
### Fixes
|
| 153 |
+
|
| 154 |
+
- [#291](https://github.com/gradio-app/trackio/pull/291) [`3b5adc3`](https://github.com/gradio-app/trackio/commit/3b5adc3d1f452dbab7a714d235f4974782f93730) - Fix the wheel build. Thanks @pngwn!
|
| 155 |
+
|
| 156 |
+
## 0.5.1
|
| 157 |
+
|
| 158 |
+
### Fixes
|
| 159 |
+
|
| 160 |
+
- [#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,664 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.alerts import AlertLevel
|
| 21 |
+
from trackio.api import Api
|
| 22 |
+
from trackio.deploy import sync
|
| 23 |
+
from trackio.gpu import gpu_available, log_gpu
|
| 24 |
+
from trackio.histogram import Histogram
|
| 25 |
+
from trackio.imports import import_csv, import_tf_events
|
| 26 |
+
from trackio.markdown import Markdown
|
| 27 |
+
from trackio.media import (
|
| 28 |
+
TrackioAudio,
|
| 29 |
+
TrackioImage,
|
| 30 |
+
TrackioVideo,
|
| 31 |
+
get_project_media_path,
|
| 32 |
+
)
|
| 33 |
+
from trackio.run import Run
|
| 34 |
+
from trackio.sqlite_storage import SQLiteStorage
|
| 35 |
+
from trackio.table import Table
|
| 36 |
+
from trackio.typehints import UploadEntry
|
| 37 |
+
from trackio.utils import TRACKIO_DIR, TRACKIO_LOGO_DIR
|
| 38 |
+
|
| 39 |
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 40 |
+
|
| 41 |
+
warnings.filterwarnings(
|
| 42 |
+
"ignore",
|
| 43 |
+
message="Empty session being created. Install gradio\\[oauth\\]",
|
| 44 |
+
category=UserWarning,
|
| 45 |
+
module="gradio.helpers",
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
__version__ = json.loads(Path(__file__).parent.joinpath("package.json").read_text())[
|
| 49 |
+
"version"
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
__all__ = [
|
| 53 |
+
"init",
|
| 54 |
+
"log",
|
| 55 |
+
"log_system",
|
| 56 |
+
"log_gpu",
|
| 57 |
+
"finish",
|
| 58 |
+
"alert",
|
| 59 |
+
"AlertLevel",
|
| 60 |
+
"show",
|
| 61 |
+
"sync",
|
| 62 |
+
"delete_project",
|
| 63 |
+
"import_csv",
|
| 64 |
+
"import_tf_events",
|
| 65 |
+
"save",
|
| 66 |
+
"Image",
|
| 67 |
+
"Video",
|
| 68 |
+
"Audio",
|
| 69 |
+
"Table",
|
| 70 |
+
"Histogram",
|
| 71 |
+
"Markdown",
|
| 72 |
+
"Api",
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
Image = TrackioImage
|
| 76 |
+
Video = TrackioVideo
|
| 77 |
+
Audio = TrackioAudio
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
config = {}
|
| 81 |
+
|
| 82 |
+
_atexit_registered = False
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _cleanup_current_run():
|
| 86 |
+
run = context_vars.current_run.get()
|
| 87 |
+
if run is not None:
|
| 88 |
+
try:
|
| 89 |
+
run.finish()
|
| 90 |
+
except Exception:
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _get_demo():
|
| 95 |
+
# Lazy import to avoid initializing Gradio Blocks (and FastAPI) at import time,
|
| 96 |
+
# which causes import lock errors for libraries that just `import trackio`.
|
| 97 |
+
from trackio.ui.main import CSS, HEAD, demo
|
| 98 |
+
|
| 99 |
+
return demo, CSS, HEAD
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def init(
|
| 103 |
+
project: str,
|
| 104 |
+
name: str | None = None,
|
| 105 |
+
group: str | None = None,
|
| 106 |
+
space_id: str | None = None,
|
| 107 |
+
space_storage: SpaceStorage | None = None,
|
| 108 |
+
dataset_id: str | None = None,
|
| 109 |
+
config: dict | None = None,
|
| 110 |
+
resume: str = "never",
|
| 111 |
+
settings: Any = None,
|
| 112 |
+
private: bool | None = None,
|
| 113 |
+
embed: bool = True,
|
| 114 |
+
auto_log_gpu: bool | None = None,
|
| 115 |
+
gpu_log_interval: float = 10.0,
|
| 116 |
+
webhook_url: str | None = None,
|
| 117 |
+
webhook_min_level: AlertLevel | str | None = None,
|
| 118 |
+
) -> Run:
|
| 119 |
+
"""
|
| 120 |
+
Creates a new Trackio project and returns a [`Run`] object.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
project (`str`):
|
| 124 |
+
The name of the project (can be an existing project to continue tracking or
|
| 125 |
+
a new project to start tracking from scratch).
|
| 126 |
+
name (`str`, *optional*):
|
| 127 |
+
The name of the run (if not provided, a default name will be generated).
|
| 128 |
+
group (`str`, *optional*):
|
| 129 |
+
The name of the group which this run belongs to in order to help organize
|
| 130 |
+
related runs together. You can toggle the entire group's visibilitiy in the
|
| 131 |
+
dashboard.
|
| 132 |
+
space_id (`str`, *optional*):
|
| 133 |
+
If provided, the project will be logged to a Hugging Face Space instead of
|
| 134 |
+
a local directory. Should be a complete Space name like
|
| 135 |
+
`"username/reponame"` or `"orgname/reponame"`, or just `"reponame"` in which
|
| 136 |
+
case the Space will be created in the currently-logged-in Hugging Face
|
| 137 |
+
user's namespace. If the Space does not exist, it will be created. If the
|
| 138 |
+
Space already exists, the project will be logged to it.
|
| 139 |
+
space_storage ([`~huggingface_hub.SpaceStorage`], *optional*):
|
| 140 |
+
Choice of persistent storage tier.
|
| 141 |
+
dataset_id (`str`, *optional*):
|
| 142 |
+
If a `space_id` is provided, a persistent Hugging Face Dataset will be
|
| 143 |
+
created and the metrics will be synced to it every 5 minutes. Specify a
|
| 144 |
+
Dataset with name like `"username/datasetname"` or `"orgname/datasetname"`,
|
| 145 |
+
or `"datasetname"` (uses currently-logged-in Hugging Face user's namespace),
|
| 146 |
+
or `None` (uses the same name as the Space but with the `"_dataset"`
|
| 147 |
+
suffix). If the Dataset does not exist, it will be created. If the Dataset
|
| 148 |
+
already exists, the project will be appended to it.
|
| 149 |
+
config (`dict`, *optional*):
|
| 150 |
+
A dictionary of configuration options. Provided for compatibility with
|
| 151 |
+
`wandb.init()`.
|
| 152 |
+
resume (`str`, *optional*, defaults to `"never"`):
|
| 153 |
+
Controls how to handle resuming a run. Can be one of:
|
| 154 |
+
|
| 155 |
+
- `"must"`: Must resume the run with the given name, raises error if run
|
| 156 |
+
doesn't exist
|
| 157 |
+
- `"allow"`: Resume the run if it exists, otherwise create a new run
|
| 158 |
+
- `"never"`: Never resume a run, always create a new one
|
| 159 |
+
private (`bool`, *optional*):
|
| 160 |
+
Whether to make the Space private. If None (default), the repo will be
|
| 161 |
+
public unless the organization's default is private. This value is ignored
|
| 162 |
+
if the repo already exists.
|
| 163 |
+
settings (`Any`, *optional*):
|
| 164 |
+
Not used. Provided for compatibility with `wandb.init()`.
|
| 165 |
+
embed (`bool`, *optional*, defaults to `True`):
|
| 166 |
+
If running inside a Jupyter/Colab notebook, whether the dashboard should
|
| 167 |
+
automatically be embedded in the cell when trackio.init() is called. For
|
| 168 |
+
local runs, this launches a local Gradio app and embeds it. For Space runs,
|
| 169 |
+
this embeds the Space URL. In Colab, the local dashboard will be accessible
|
| 170 |
+
via a public share URL (default Gradio behavior).
|
| 171 |
+
auto_log_gpu (`bool` or `None`, *optional*, defaults to `None`):
|
| 172 |
+
Controls automatic GPU metrics logging. If `None` (default), GPU logging
|
| 173 |
+
is automatically enabled when `nvidia-ml-py` is installed and an NVIDIA
|
| 174 |
+
GPU is detected. Set to `True` to force enable or `False` to disable.
|
| 175 |
+
gpu_log_interval (`float`, *optional*, defaults to `10.0`):
|
| 176 |
+
The interval in seconds between automatic GPU metric logs.
|
| 177 |
+
Only used when `auto_log_gpu=True`.
|
| 178 |
+
webhook_url (`str`, *optional*):
|
| 179 |
+
A webhook URL to POST alert payloads to when `trackio.alert()` is
|
| 180 |
+
called. Supports Slack and Discord webhook URLs natively (payloads
|
| 181 |
+
are formatted automatically). Can also be set via the
|
| 182 |
+
`TRACKIO_WEBHOOK_URL` environment variable. Individual alerts can
|
| 183 |
+
override this URL by passing `webhook_url` to `trackio.alert()`.
|
| 184 |
+
webhook_min_level (`AlertLevel` or `str`, *optional*):
|
| 185 |
+
Minimum alert level that should trigger webhook delivery.
|
| 186 |
+
For example, `AlertLevel.WARN` sends only `WARN` and `ERROR`
|
| 187 |
+
alerts to the webhook destination. Can also be set via
|
| 188 |
+
`TRACKIO_WEBHOOK_MIN_LEVEL`.
|
| 189 |
+
Returns:
|
| 190 |
+
`Run`: A [`Run`] object that can be used to log metrics and finish the run.
|
| 191 |
+
"""
|
| 192 |
+
if settings is not None:
|
| 193 |
+
warnings.warn(
|
| 194 |
+
"* 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."
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
if space_id is None and dataset_id is not None:
|
| 198 |
+
raise ValueError("Must provide a `space_id` when `dataset_id` is provided.")
|
| 199 |
+
try:
|
| 200 |
+
space_id, dataset_id = utils.preprocess_space_and_dataset_ids(
|
| 201 |
+
space_id, dataset_id
|
| 202 |
+
)
|
| 203 |
+
except LocalTokenNotFoundError as e:
|
| 204 |
+
raise LocalTokenNotFoundError(
|
| 205 |
+
f"You must be logged in to Hugging Face locally when `space_id` is provided to deploy to a Space. {e}"
|
| 206 |
+
) from e
|
| 207 |
+
|
| 208 |
+
url = context_vars.current_server.get()
|
| 209 |
+
|
| 210 |
+
if space_id is not None:
|
| 211 |
+
if url is None:
|
| 212 |
+
url = space_id
|
| 213 |
+
context_vars.current_server.set(url)
|
| 214 |
+
context_vars.current_space_id.set(space_id)
|
| 215 |
+
|
| 216 |
+
_should_embed_local = False
|
| 217 |
+
|
| 218 |
+
if (
|
| 219 |
+
context_vars.current_project.get() is None
|
| 220 |
+
or context_vars.current_project.get() != project
|
| 221 |
+
):
|
| 222 |
+
print(f"* Trackio project initialized: {project}")
|
| 223 |
+
|
| 224 |
+
if dataset_id is not None:
|
| 225 |
+
os.environ["TRACKIO_DATASET_ID"] = dataset_id
|
| 226 |
+
print(
|
| 227 |
+
f"* Trackio metrics will be synced to Hugging Face Dataset: {dataset_id}"
|
| 228 |
+
)
|
| 229 |
+
if space_id is None:
|
| 230 |
+
print(f"* Trackio metrics logged to: {TRACKIO_DIR}")
|
| 231 |
+
_should_embed_local = embed and utils.is_in_notebook()
|
| 232 |
+
if not _should_embed_local:
|
| 233 |
+
utils.print_dashboard_instructions(project)
|
| 234 |
+
else:
|
| 235 |
+
deploy.create_space_if_not_exists(
|
| 236 |
+
space_id, space_storage, dataset_id, private
|
| 237 |
+
)
|
| 238 |
+
user_name, space_name = space_id.split("/")
|
| 239 |
+
space_url = deploy.SPACE_HOST_URL.format(
|
| 240 |
+
user_name=user_name, space_name=space_name
|
| 241 |
+
)
|
| 242 |
+
print(f"* View dashboard by going to: {space_url}")
|
| 243 |
+
if utils.is_in_notebook() and embed:
|
| 244 |
+
utils.embed_url_in_notebook(space_url)
|
| 245 |
+
context_vars.current_project.set(project)
|
| 246 |
+
|
| 247 |
+
if resume == "must":
|
| 248 |
+
if name is None:
|
| 249 |
+
raise ValueError("Must provide a run name when resume='must'")
|
| 250 |
+
if name not in SQLiteStorage.get_runs(project):
|
| 251 |
+
raise ValueError(f"Run '{name}' does not exist in project '{project}'")
|
| 252 |
+
resumed = True
|
| 253 |
+
elif resume == "allow":
|
| 254 |
+
resumed = name is not None and name in SQLiteStorage.get_runs(project)
|
| 255 |
+
elif resume == "never":
|
| 256 |
+
if name is not None and name in SQLiteStorage.get_runs(project):
|
| 257 |
+
warnings.warn(
|
| 258 |
+
f"* Warning: resume='never' but a run '{name}' already exists in "
|
| 259 |
+
f"project '{project}'. Generating a new name and instead. If you want "
|
| 260 |
+
"to resume this run, call init() with resume='must' or resume='allow'."
|
| 261 |
+
)
|
| 262 |
+
name = None
|
| 263 |
+
resumed = False
|
| 264 |
+
else:
|
| 265 |
+
raise ValueError("resume must be one of: 'must', 'allow', or 'never'")
|
| 266 |
+
|
| 267 |
+
if auto_log_gpu is None:
|
| 268 |
+
auto_log_gpu = gpu_available()
|
| 269 |
+
if auto_log_gpu:
|
| 270 |
+
print("* GPU detected, enabling automatic GPU metrics logging")
|
| 271 |
+
|
| 272 |
+
run = Run(
|
| 273 |
+
url=url,
|
| 274 |
+
project=project,
|
| 275 |
+
client=None,
|
| 276 |
+
name=name,
|
| 277 |
+
group=group,
|
| 278 |
+
config=config,
|
| 279 |
+
space_id=space_id,
|
| 280 |
+
auto_log_gpu=auto_log_gpu,
|
| 281 |
+
gpu_log_interval=gpu_log_interval,
|
| 282 |
+
webhook_url=webhook_url,
|
| 283 |
+
webhook_min_level=webhook_min_level,
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
if space_id is not None:
|
| 287 |
+
SQLiteStorage.set_project_metadata(project, "space_id", space_id)
|
| 288 |
+
if SQLiteStorage.has_pending_data(project):
|
| 289 |
+
run._has_local_buffer = True
|
| 290 |
+
|
| 291 |
+
global _atexit_registered
|
| 292 |
+
if not _atexit_registered:
|
| 293 |
+
atexit.register(_cleanup_current_run)
|
| 294 |
+
_atexit_registered = True
|
| 295 |
+
|
| 296 |
+
if resumed:
|
| 297 |
+
print(f"* Resumed existing run: {run.name}")
|
| 298 |
+
else:
|
| 299 |
+
print(f"* Created new run: {run.name}")
|
| 300 |
+
|
| 301 |
+
context_vars.current_run.set(run)
|
| 302 |
+
globals()["config"] = run.config
|
| 303 |
+
|
| 304 |
+
if _should_embed_local:
|
| 305 |
+
show(project=project, open_browser=False, block_thread=False)
|
| 306 |
+
|
| 307 |
+
return run
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def log(metrics: dict, step: int | None = None) -> None:
|
| 311 |
+
"""
|
| 312 |
+
Logs metrics to the current run.
|
| 313 |
+
|
| 314 |
+
Args:
|
| 315 |
+
metrics (`dict`):
|
| 316 |
+
A dictionary of metrics to log.
|
| 317 |
+
step (`int`, *optional*):
|
| 318 |
+
The step number. If not provided, the step will be incremented
|
| 319 |
+
automatically.
|
| 320 |
+
"""
|
| 321 |
+
run = context_vars.current_run.get()
|
| 322 |
+
if run is None:
|
| 323 |
+
raise RuntimeError("Call trackio.init() before trackio.log().")
|
| 324 |
+
run.log(
|
| 325 |
+
metrics=metrics,
|
| 326 |
+
step=step,
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def log_system(metrics: dict) -> None:
|
| 331 |
+
"""
|
| 332 |
+
Logs system metrics (GPU, etc.) to the current run using timestamps instead of steps.
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
metrics (`dict`):
|
| 336 |
+
A dictionary of system metrics to log.
|
| 337 |
+
"""
|
| 338 |
+
run = context_vars.current_run.get()
|
| 339 |
+
if run is None:
|
| 340 |
+
raise RuntimeError("Call trackio.init() before trackio.log_system().")
|
| 341 |
+
run.log_system(metrics=metrics)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def finish():
|
| 345 |
+
"""
|
| 346 |
+
Finishes the current run.
|
| 347 |
+
"""
|
| 348 |
+
run = context_vars.current_run.get()
|
| 349 |
+
if run is None:
|
| 350 |
+
raise RuntimeError("Call trackio.init() before trackio.finish().")
|
| 351 |
+
run.finish()
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def alert(
|
| 355 |
+
title: str,
|
| 356 |
+
text: str | None = None,
|
| 357 |
+
level: AlertLevel = AlertLevel.WARN,
|
| 358 |
+
webhook_url: str | None = None,
|
| 359 |
+
) -> None:
|
| 360 |
+
"""
|
| 361 |
+
Fires an alert immediately on the current run. The alert is printed to the
|
| 362 |
+
terminal, stored in the database, and displayed in the dashboard. If a
|
| 363 |
+
webhook URL is configured (via `trackio.init()`, the `TRACKIO_WEBHOOK_URL`
|
| 364 |
+
environment variable, or the `webhook_url` parameter here), the alert is
|
| 365 |
+
also POSTed to that URL.
|
| 366 |
+
|
| 367 |
+
Args:
|
| 368 |
+
title (`str`):
|
| 369 |
+
A short title for the alert.
|
| 370 |
+
text (`str`, *optional*):
|
| 371 |
+
A longer description with details about the alert.
|
| 372 |
+
level (`AlertLevel`, *optional*, defaults to `AlertLevel.WARN`):
|
| 373 |
+
The severity level. One of `AlertLevel.INFO`, `AlertLevel.WARN`,
|
| 374 |
+
or `AlertLevel.ERROR`.
|
| 375 |
+
webhook_url (`str`, *optional*):
|
| 376 |
+
A webhook URL to send this specific alert to. Overrides any
|
| 377 |
+
URL set in `trackio.init()` or the `TRACKIO_WEBHOOK_URL`
|
| 378 |
+
environment variable. Supports Slack and Discord webhook
|
| 379 |
+
URLs natively.
|
| 380 |
+
"""
|
| 381 |
+
run = context_vars.current_run.get()
|
| 382 |
+
if run is None:
|
| 383 |
+
raise RuntimeError("Call trackio.init() before trackio.alert().")
|
| 384 |
+
run.alert(title=title, text=text, level=level, webhook_url=webhook_url)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def delete_project(project: str, force: bool = False) -> bool:
|
| 388 |
+
"""
|
| 389 |
+
Deletes a project by removing its local SQLite database.
|
| 390 |
+
|
| 391 |
+
Args:
|
| 392 |
+
project (`str`):
|
| 393 |
+
The name of the project to delete.
|
| 394 |
+
force (`bool`, *optional*, defaults to `False`):
|
| 395 |
+
If `True`, deletes the project without prompting for confirmation.
|
| 396 |
+
If `False`, prompts the user to confirm before deleting.
|
| 397 |
+
|
| 398 |
+
Returns:
|
| 399 |
+
`bool`: `True` if the project was deleted, `False` otherwise.
|
| 400 |
+
"""
|
| 401 |
+
db_path = SQLiteStorage.get_project_db_path(project)
|
| 402 |
+
|
| 403 |
+
if not db_path.exists():
|
| 404 |
+
print(f"* Project '{project}' does not exist.")
|
| 405 |
+
return False
|
| 406 |
+
|
| 407 |
+
if not force:
|
| 408 |
+
response = input(
|
| 409 |
+
f"Are you sure you want to delete project '{project}'? "
|
| 410 |
+
f"This will permanently delete all runs and metrics. (y/N): "
|
| 411 |
+
)
|
| 412 |
+
if response.lower() not in ["y", "yes"]:
|
| 413 |
+
print("* Deletion cancelled.")
|
| 414 |
+
return False
|
| 415 |
+
|
| 416 |
+
try:
|
| 417 |
+
db_path.unlink()
|
| 418 |
+
|
| 419 |
+
for suffix in ("-wal", "-shm"):
|
| 420 |
+
sidecar = Path(str(db_path) + suffix)
|
| 421 |
+
if sidecar.exists():
|
| 422 |
+
sidecar.unlink()
|
| 423 |
+
|
| 424 |
+
print(f"* Project '{project}' has been deleted.")
|
| 425 |
+
return True
|
| 426 |
+
except Exception as e:
|
| 427 |
+
print(f"* Error deleting project '{project}': {e}")
|
| 428 |
+
return False
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def save(
|
| 432 |
+
glob_str: str | Path,
|
| 433 |
+
project: str | None = None,
|
| 434 |
+
) -> str:
|
| 435 |
+
"""
|
| 436 |
+
Saves files to a project (not linked to a specific run). If Trackio is running
|
| 437 |
+
locally, the file(s) will be copied to the project's files directory. If Trackio is
|
| 438 |
+
running in a Space, the file(s) will be uploaded to the Space's files directory.
|
| 439 |
+
|
| 440 |
+
Args:
|
| 441 |
+
glob_str (`str` or `Path`):
|
| 442 |
+
The file path or glob pattern to save. Can be a single file or a pattern
|
| 443 |
+
matching multiple files (e.g., `"*.py"`, `"models/**/*.pth"`).
|
| 444 |
+
project (`str`, *optional*):
|
| 445 |
+
The name of the project to save files to. If not provided, uses the current
|
| 446 |
+
project from `trackio.init()`. If no project is initialized, raises an
|
| 447 |
+
error.
|
| 448 |
+
|
| 449 |
+
Returns:
|
| 450 |
+
`str`: The path where the file(s) were saved (project's files directory).
|
| 451 |
+
|
| 452 |
+
Example:
|
| 453 |
+
```python
|
| 454 |
+
import trackio
|
| 455 |
+
|
| 456 |
+
trackio.init(project="my-project")
|
| 457 |
+
trackio.save("config.yaml")
|
| 458 |
+
trackio.save("models/*.pth")
|
| 459 |
+
```
|
| 460 |
+
"""
|
| 461 |
+
if project is None:
|
| 462 |
+
project = context_vars.current_project.get()
|
| 463 |
+
if project is None:
|
| 464 |
+
raise RuntimeError(
|
| 465 |
+
"No project specified. Either call trackio.init() first or provide a "
|
| 466 |
+
"project parameter to trackio.save()."
|
| 467 |
+
)
|
| 468 |
+
|
| 469 |
+
glob_str = Path(glob_str)
|
| 470 |
+
base_path = Path.cwd().resolve()
|
| 471 |
+
|
| 472 |
+
matched_files = []
|
| 473 |
+
if glob_str.is_file():
|
| 474 |
+
matched_files = [glob_str.resolve()]
|
| 475 |
+
else:
|
| 476 |
+
pattern = str(glob_str)
|
| 477 |
+
if not glob_str.is_absolute():
|
| 478 |
+
pattern = str((Path.cwd() / glob_str).resolve())
|
| 479 |
+
matched_files = [
|
| 480 |
+
Path(f).resolve()
|
| 481 |
+
for f in glob.glob(pattern, recursive=True)
|
| 482 |
+
if Path(f).is_file()
|
| 483 |
+
]
|
| 484 |
+
|
| 485 |
+
if not matched_files:
|
| 486 |
+
raise ValueError(f"No files found matching pattern: {glob_str}")
|
| 487 |
+
|
| 488 |
+
current_run = context_vars.current_run.get()
|
| 489 |
+
is_local = (
|
| 490 |
+
current_run._is_local
|
| 491 |
+
if current_run is not None
|
| 492 |
+
else (context_vars.current_space_id.get() is None)
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
if is_local:
|
| 496 |
+
for file_path in matched_files:
|
| 497 |
+
try:
|
| 498 |
+
relative_to_base = file_path.relative_to(base_path)
|
| 499 |
+
except ValueError:
|
| 500 |
+
relative_to_base = Path(file_path.name)
|
| 501 |
+
|
| 502 |
+
if current_run is not None:
|
| 503 |
+
current_run._queue_upload(
|
| 504 |
+
file_path,
|
| 505 |
+
step=None,
|
| 506 |
+
relative_path=str(relative_to_base.parent),
|
| 507 |
+
use_run_name=False,
|
| 508 |
+
)
|
| 509 |
+
else:
|
| 510 |
+
media_path = get_project_media_path(
|
| 511 |
+
project=project,
|
| 512 |
+
run=None,
|
| 513 |
+
step=None,
|
| 514 |
+
relative_path=str(relative_to_base),
|
| 515 |
+
)
|
| 516 |
+
shutil.copy(str(file_path), str(media_path))
|
| 517 |
+
else:
|
| 518 |
+
url = context_vars.current_server.get()
|
| 519 |
+
|
| 520 |
+
upload_entries = []
|
| 521 |
+
for file_path in matched_files:
|
| 522 |
+
try:
|
| 523 |
+
relative_to_base = file_path.relative_to(base_path)
|
| 524 |
+
except ValueError:
|
| 525 |
+
relative_to_base = Path(file_path.name)
|
| 526 |
+
|
| 527 |
+
if current_run is not None:
|
| 528 |
+
current_run._queue_upload(
|
| 529 |
+
file_path,
|
| 530 |
+
step=None,
|
| 531 |
+
relative_path=str(relative_to_base.parent),
|
| 532 |
+
use_run_name=False,
|
| 533 |
+
)
|
| 534 |
+
else:
|
| 535 |
+
upload_entry: UploadEntry = {
|
| 536 |
+
"project": project,
|
| 537 |
+
"run": None,
|
| 538 |
+
"step": None,
|
| 539 |
+
"relative_path": str(relative_to_base),
|
| 540 |
+
"uploaded_file": handle_file(file_path),
|
| 541 |
+
}
|
| 542 |
+
upload_entries.append(upload_entry)
|
| 543 |
+
|
| 544 |
+
if upload_entries:
|
| 545 |
+
if url is None:
|
| 546 |
+
raise RuntimeError(
|
| 547 |
+
"No server available. Call trackio.init() before trackio.save() to start the server."
|
| 548 |
+
)
|
| 549 |
+
|
| 550 |
+
try:
|
| 551 |
+
client = Client(url, verbose=False, httpx_kwargs={"timeout": 90})
|
| 552 |
+
client.predict(
|
| 553 |
+
api_name="/bulk_upload_media",
|
| 554 |
+
uploads=upload_entries,
|
| 555 |
+
hf_token=huggingface_hub.utils.get_token(),
|
| 556 |
+
)
|
| 557 |
+
except Exception as e:
|
| 558 |
+
warnings.warn(
|
| 559 |
+
f"Failed to upload files: {e}. "
|
| 560 |
+
"Files may not be available in the dashboard."
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
return str(utils.MEDIA_DIR / project / "files")
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
def show(
|
| 567 |
+
project: str | None = None,
|
| 568 |
+
*,
|
| 569 |
+
theme: str | ThemeClass | None = None,
|
| 570 |
+
mcp_server: bool | None = None,
|
| 571 |
+
footer: bool = True,
|
| 572 |
+
color_palette: list[str] | None = None,
|
| 573 |
+
open_browser: bool = True,
|
| 574 |
+
block_thread: bool | None = None,
|
| 575 |
+
host: str | None = None,
|
| 576 |
+
):
|
| 577 |
+
"""
|
| 578 |
+
Launches the Trackio dashboard.
|
| 579 |
+
|
| 580 |
+
Args:
|
| 581 |
+
project (`str`, *optional*):
|
| 582 |
+
The name of the project whose runs to show. If not provided, all projects
|
| 583 |
+
will be shown and the user can select one.
|
| 584 |
+
theme (`str` or `ThemeClass`, *optional*):
|
| 585 |
+
A Gradio Theme to use for the dashboard instead of the default Gradio theme,
|
| 586 |
+
can be a built-in theme (e.g. `'soft'`, `'citrus'`), a theme from the Hub
|
| 587 |
+
(e.g. `"gstaff/xkcd"`), or a custom Theme class. If not provided, the
|
| 588 |
+
`TRACKIO_THEME` environment variable will be used, or if that is not set,
|
| 589 |
+
the default Gradio theme will be used.
|
| 590 |
+
mcp_server (`bool`, *optional*):
|
| 591 |
+
If `True`, the Trackio dashboard will be set up as an MCP server and certain
|
| 592 |
+
functions will be added as MCP tools. If `None` (default behavior), then the
|
| 593 |
+
`GRADIO_MCP_SERVER` environment variable will be used to determine if the
|
| 594 |
+
MCP server should be enabled (which is `"True"` on Hugging Face Spaces).
|
| 595 |
+
footer (`bool`, *optional*, defaults to `True`):
|
| 596 |
+
Whether to show the Gradio footer. When `False`, the footer will be hidden.
|
| 597 |
+
This can also be controlled via the `footer` query parameter in the URL.
|
| 598 |
+
color_palette (`list[str]`, *optional*):
|
| 599 |
+
A list of hex color codes to use for plot lines. If not provided, the
|
| 600 |
+
`TRACKIO_COLOR_PALETTE` environment variable will be used (comma-separated
|
| 601 |
+
hex codes), or if that is not set, the default color palette will be used.
|
| 602 |
+
Example: `['#FF0000', '#00FF00', '#0000FF']`
|
| 603 |
+
open_browser (`bool`, *optional*, defaults to `True`):
|
| 604 |
+
If `True` and not in a notebook, a new browser tab will be opened with the
|
| 605 |
+
dashboard. If `False`, the browser will not be opened.
|
| 606 |
+
block_thread (`bool`, *optional*):
|
| 607 |
+
If `True`, the main thread will be blocked until the dashboard is closed.
|
| 608 |
+
If `None` (default behavior), then the main thread will not be blocked if the
|
| 609 |
+
dashboard is launched in a notebook, otherwise the main thread will be blocked.
|
| 610 |
+
host (`str`, *optional*):
|
| 611 |
+
The host to bind the server to. If not provided, defaults to `'127.0.0.1'`
|
| 612 |
+
(localhost only). Set to `'0.0.0.0'` to allow remote access.
|
| 613 |
+
|
| 614 |
+
Returns:
|
| 615 |
+
`app`: The Gradio app object corresponding to the dashboard launched by Trackio.
|
| 616 |
+
`url`: The local URL of the dashboard.
|
| 617 |
+
`share_url`: The public share URL of the dashboard.
|
| 618 |
+
`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).
|
| 619 |
+
"""
|
| 620 |
+
demo, CSS, HEAD = _get_demo()
|
| 621 |
+
|
| 622 |
+
if color_palette is not None:
|
| 623 |
+
os.environ["TRACKIO_COLOR_PALETTE"] = ",".join(color_palette)
|
| 624 |
+
|
| 625 |
+
theme = theme or os.environ.get("TRACKIO_THEME")
|
| 626 |
+
|
| 627 |
+
_mcp_server = (
|
| 628 |
+
mcp_server
|
| 629 |
+
if mcp_server is not None
|
| 630 |
+
else os.environ.get("GRADIO_MCP_SERVER", "False") == "True"
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
app, url, share_url = demo.launch(
|
| 634 |
+
css=CSS,
|
| 635 |
+
head=HEAD,
|
| 636 |
+
footer_links=["gradio", "settings"] + (["api"] if _mcp_server else []),
|
| 637 |
+
quiet=True,
|
| 638 |
+
inline=False,
|
| 639 |
+
prevent_thread_lock=True,
|
| 640 |
+
favicon_path=TRACKIO_LOGO_DIR / "trackio_logo_light.png",
|
| 641 |
+
allowed_paths=[TRACKIO_LOGO_DIR, TRACKIO_DIR],
|
| 642 |
+
mcp_server=_mcp_server,
|
| 643 |
+
theme=theme,
|
| 644 |
+
ssr_mode=False,
|
| 645 |
+
server_name=host,
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
base_url = share_url + "/" if share_url else url
|
| 649 |
+
full_url = utils.get_full_url(
|
| 650 |
+
base_url, project=project, write_token=demo.write_token, footer=footer
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
if not utils.is_in_notebook():
|
| 654 |
+
print(f"* Trackio UI launched at: {full_url}")
|
| 655 |
+
if open_browser:
|
| 656 |
+
webbrowser.open(full_url)
|
| 657 |
+
block_thread = block_thread if block_thread is not None else True
|
| 658 |
+
else:
|
| 659 |
+
utils.embed_url_in_notebook(full_url)
|
| 660 |
+
block_thread = block_thread if block_thread is not None else False
|
| 661 |
+
|
| 662 |
+
if block_thread:
|
| 663 |
+
utils.block_main_thread_until_keyboard_interrupt()
|
| 664 |
+
return TupleNoPrint((demo, url, share_url, full_url))
|
trackio/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (23.7 kB). View file
|
|
|
trackio/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (27.1 kB). View file
|
|
|
trackio/__pycache__/alerts.cpython-313.pyc
ADDED
|
Binary file (7.51 kB). View file
|
|
|
trackio/__pycache__/api.cpython-312.pyc
ADDED
|
Binary file (4.42 kB). View file
|
|
|
trackio/__pycache__/api.cpython-313.pyc
ADDED
|
Binary file (5.86 kB). View file
|
|
|
trackio/__pycache__/cli.cpython-312.pyc
ADDED
|
Binary file (1.71 kB). View file
|
|
|
trackio/__pycache__/cli.cpython-313.pyc
ADDED
|
Binary file (23.3 kB). View file
|
|
|
trackio/__pycache__/cli_helpers.cpython-313.pyc
ADDED
|
Binary file (6.04 kB). View file
|
|
|
trackio/__pycache__/commit_scheduler.cpython-312.pyc
ADDED
|
Binary file (18.8 kB). View file
|
|
|
trackio/__pycache__/commit_scheduler.cpython-313.pyc
ADDED
|
Binary file (14.1 kB). View file
|
|
|
trackio/__pycache__/context_vars.cpython-312.pyc
ADDED
|
Binary file (1.07 kB). View file
|
|
|
trackio/__pycache__/context_vars.cpython-313.pyc
ADDED
|
Binary file (892 Bytes). View file
|
|
|
trackio/__pycache__/deploy.cpython-312.pyc
ADDED
|
Binary file (14.8 kB). View file
|
|
|
trackio/__pycache__/deploy.cpython-313.pyc
ADDED
|
Binary file (18.8 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.7 kB). View file
|
|
|
trackio/__pycache__/gpu.cpython-313.pyc
ADDED
|
Binary file (14.3 kB). View file
|
|
|
trackio/__pycache__/histogram.cpython-312.pyc
ADDED
|
Binary file (3.23 kB). View file
|
|
|
trackio/__pycache__/histogram.cpython-313.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 (13.2 kB). View file
|
|
|
trackio/__pycache__/markdown.cpython-313.pyc
ADDED
|
Binary file (1.09 kB). View file
|
|
|
trackio/__pycache__/media.cpython-312.pyc
ADDED
|
Binary file (14.1 kB). View file
|
|
|
trackio/__pycache__/run.cpython-312.pyc
ADDED
|
Binary file (14.7 kB). View file
|
|
|
trackio/__pycache__/run.cpython-313.pyc
ADDED
|
Binary file (33.6 kB). View file
|
|
|
trackio/__pycache__/sqlite_storage.cpython-312.pyc
ADDED
|
Binary file (49.4 kB). View file
|
|
|
trackio/__pycache__/sqlite_storage.cpython-313.pyc
ADDED
|
Binary file (79.1 kB). View file
|
|
|
trackio/__pycache__/table.cpython-312.pyc
ADDED
|
Binary file (8.59 kB). View file
|
|
|
trackio/__pycache__/table.cpython-313.pyc
ADDED
|
Binary file (8.51 kB). View file
|
|
|
trackio/__pycache__/typehints.cpython-312.pyc
ADDED
|
Binary file (1.24 kB). View file
|
|
|
trackio/__pycache__/typehints.cpython-313.pyc
ADDED
|
Binary file (1.83 kB). View file
|
|
|
trackio/__pycache__/ui.cpython-312.pyc
ADDED
|
Binary file (32.6 kB). View file
|
|
|
trackio/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (29.8 kB). View file
|
|
|
trackio/__pycache__/utils.cpython-313.pyc
ADDED
|
Binary file (30.1 kB). View file
|
|
|
trackio/__pycache__/video_writer.cpython-312.pyc
ADDED
|
Binary file (5.32 kB). View file
|
|
|
trackio/alerts.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import logging
|
| 3 |
+
import ssl
|
| 4 |
+
import urllib.error
|
| 5 |
+
import urllib.request
|
| 6 |
+
from enum import Enum
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
import certifi
|
| 10 |
+
|
| 11 |
+
_SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
|
| 12 |
+
except ImportError:
|
| 13 |
+
_SSL_CONTEXT = None
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class AlertLevel(str, Enum):
|
| 19 |
+
INFO = "info"
|
| 20 |
+
WARN = "warn"
|
| 21 |
+
ERROR = "error"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ALERT_LEVEL_ORDER = {
|
| 25 |
+
AlertLevel.INFO: 0,
|
| 26 |
+
AlertLevel.WARN: 1,
|
| 27 |
+
AlertLevel.ERROR: 2,
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
ALERT_COLORS = {
|
| 31 |
+
AlertLevel.INFO: "\033[94m",
|
| 32 |
+
AlertLevel.WARN: "\033[93m",
|
| 33 |
+
AlertLevel.ERROR: "\033[91m",
|
| 34 |
+
}
|
| 35 |
+
RESET_COLOR = "\033[0m"
|
| 36 |
+
|
| 37 |
+
LEVEL_EMOJI = {
|
| 38 |
+
AlertLevel.INFO: "ℹ️",
|
| 39 |
+
AlertLevel.WARN: "⚠️",
|
| 40 |
+
AlertLevel.ERROR: "🚨",
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def format_alert_terminal(
|
| 45 |
+
level: AlertLevel, title: str, text: str | None, step: int | None
|
| 46 |
+
) -> str:
|
| 47 |
+
color = ALERT_COLORS.get(level, "")
|
| 48 |
+
step_str = f" (step {step})" if step is not None else ""
|
| 49 |
+
if text:
|
| 50 |
+
return f"{color}[TRACKIO {level.value.upper()}]{RESET_COLOR} {title}: {text}{step_str}"
|
| 51 |
+
return f"{color}[TRACKIO {level.value.upper()}]{RESET_COLOR} {title}{step_str}"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _is_slack_url(url: str) -> bool:
|
| 55 |
+
return "hooks.slack.com" in url
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _is_discord_url(url: str) -> bool:
|
| 59 |
+
return "discord.com/api/webhooks" in url or "discordapp.com/api/webhooks" in url
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _build_slack_payload(
|
| 63 |
+
level: AlertLevel,
|
| 64 |
+
title: str,
|
| 65 |
+
text: str | None,
|
| 66 |
+
project: str,
|
| 67 |
+
run: str,
|
| 68 |
+
step: int | None,
|
| 69 |
+
) -> dict:
|
| 70 |
+
emoji = LEVEL_EMOJI.get(level, "")
|
| 71 |
+
step_str = f" • Step {step}" if step is not None else ""
|
| 72 |
+
header = f"{emoji} *[{level.value.upper()}] {title}*"
|
| 73 |
+
context = f"Project: {project} • Run: {run}{step_str}"
|
| 74 |
+
blocks = [
|
| 75 |
+
{"type": "section", "text": {"type": "mrkdwn", "text": header}},
|
| 76 |
+
]
|
| 77 |
+
if text:
|
| 78 |
+
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": text}})
|
| 79 |
+
blocks.append(
|
| 80 |
+
{"type": "context", "elements": [{"type": "mrkdwn", "text": context}]}
|
| 81 |
+
)
|
| 82 |
+
return {"blocks": blocks}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _build_discord_payload(
|
| 86 |
+
level: AlertLevel,
|
| 87 |
+
title: str,
|
| 88 |
+
text: str | None,
|
| 89 |
+
project: str,
|
| 90 |
+
run: str,
|
| 91 |
+
step: int | None,
|
| 92 |
+
) -> dict:
|
| 93 |
+
color_map = {
|
| 94 |
+
AlertLevel.INFO: 3447003,
|
| 95 |
+
AlertLevel.WARN: 16776960,
|
| 96 |
+
AlertLevel.ERROR: 15158332,
|
| 97 |
+
}
|
| 98 |
+
emoji = LEVEL_EMOJI.get(level, "")
|
| 99 |
+
step_str = f" • Step {step}" if step is not None else ""
|
| 100 |
+
embed = {
|
| 101 |
+
"title": f"{emoji} [{level.value.upper()}] {title}",
|
| 102 |
+
"color": color_map.get(level, 0),
|
| 103 |
+
"footer": {"text": f"Project: {project} • Run: {run}{step_str}"},
|
| 104 |
+
}
|
| 105 |
+
if text:
|
| 106 |
+
embed["description"] = text
|
| 107 |
+
return {"embeds": [embed]}
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _build_generic_payload(
|
| 111 |
+
level: AlertLevel,
|
| 112 |
+
title: str,
|
| 113 |
+
text: str | None,
|
| 114 |
+
project: str,
|
| 115 |
+
run: str,
|
| 116 |
+
step: int | None,
|
| 117 |
+
timestamp: str | None,
|
| 118 |
+
) -> dict:
|
| 119 |
+
return {
|
| 120 |
+
"level": level.value,
|
| 121 |
+
"title": title,
|
| 122 |
+
"text": text,
|
| 123 |
+
"project": project,
|
| 124 |
+
"run": run,
|
| 125 |
+
"step": step,
|
| 126 |
+
"timestamp": timestamp,
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def parse_alert_level(level: AlertLevel | str) -> AlertLevel:
|
| 131 |
+
if isinstance(level, AlertLevel):
|
| 132 |
+
return level
|
| 133 |
+
normalized = level.lower().strip()
|
| 134 |
+
try:
|
| 135 |
+
return AlertLevel(normalized)
|
| 136 |
+
except ValueError as e:
|
| 137 |
+
allowed = ", ".join(lvl.value for lvl in AlertLevel)
|
| 138 |
+
raise ValueError(
|
| 139 |
+
f"Invalid alert level '{level}'. Expected one of: {allowed}."
|
| 140 |
+
) from e
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def resolve_webhook_min_level(
|
| 144 |
+
webhook_min_level: AlertLevel | str | None,
|
| 145 |
+
) -> AlertLevel | None:
|
| 146 |
+
if webhook_min_level is None:
|
| 147 |
+
return None
|
| 148 |
+
return parse_alert_level(webhook_min_level)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def should_send_webhook(
|
| 152 |
+
level: AlertLevel, webhook_min_level: AlertLevel | None
|
| 153 |
+
) -> bool:
|
| 154 |
+
if webhook_min_level is None:
|
| 155 |
+
return True
|
| 156 |
+
return ALERT_LEVEL_ORDER[level] >= ALERT_LEVEL_ORDER[webhook_min_level]
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def send_webhook(
|
| 160 |
+
url: str,
|
| 161 |
+
level: AlertLevel,
|
| 162 |
+
title: str,
|
| 163 |
+
text: str | None,
|
| 164 |
+
project: str,
|
| 165 |
+
run: str,
|
| 166 |
+
step: int | None,
|
| 167 |
+
timestamp: str | None = None,
|
| 168 |
+
) -> None:
|
| 169 |
+
if _is_slack_url(url):
|
| 170 |
+
payload = _build_slack_payload(level, title, text, project, run, step)
|
| 171 |
+
elif _is_discord_url(url):
|
| 172 |
+
payload = _build_discord_payload(level, title, text, project, run, step)
|
| 173 |
+
else:
|
| 174 |
+
payload = _build_generic_payload(
|
| 175 |
+
level, title, text, project, run, step, timestamp
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
data = json.dumps(payload).encode("utf-8")
|
| 179 |
+
req = urllib.request.Request(
|
| 180 |
+
url, data=data, headers={"Content-Type": "application/json"}
|
| 181 |
+
)
|
| 182 |
+
try:
|
| 183 |
+
urllib.request.urlopen(req, timeout=10, context=_SSL_CONTEXT)
|
| 184 |
+
except Exception as e:
|
| 185 |
+
logger.warning(f"Failed to send webhook to {url}: {e}")
|
trackio/api.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 alerts(self, level: str | None = None, since: str | None = None) -> list[dict]:
|
| 23 |
+
return SQLiteStorage.get_alerts(
|
| 24 |
+
self.project, run_name=self.name, level=level, since=since
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
def delete(self) -> bool:
|
| 28 |
+
return SQLiteStorage.delete_run(self.project, self.name)
|
| 29 |
+
|
| 30 |
+
def move(self, new_project: str) -> bool:
|
| 31 |
+
success = SQLiteStorage.move_run(self.project, self.name, new_project)
|
| 32 |
+
if success:
|
| 33 |
+
self.project = new_project
|
| 34 |
+
return success
|
| 35 |
+
|
| 36 |
+
def rename(self, new_name: str) -> "Run":
|
| 37 |
+
SQLiteStorage.rename_run(self.project, self.name, new_name)
|
| 38 |
+
self.name = new_name
|
| 39 |
+
return self
|
| 40 |
+
|
| 41 |
+
def __repr__(self) -> str:
|
| 42 |
+
return f"<Run {self.name} in project {self.project}>"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Runs:
|
| 46 |
+
def __init__(self, project: str):
|
| 47 |
+
self.project = project
|
| 48 |
+
self._runs = None
|
| 49 |
+
|
| 50 |
+
def _load_runs(self):
|
| 51 |
+
if self._runs is None:
|
| 52 |
+
run_names = SQLiteStorage.get_runs(self.project)
|
| 53 |
+
self._runs = [Run(self.project, name) for name in run_names]
|
| 54 |
+
|
| 55 |
+
def __iter__(self) -> Iterator[Run]:
|
| 56 |
+
self._load_runs()
|
| 57 |
+
return iter(self._runs)
|
| 58 |
+
|
| 59 |
+
def __getitem__(self, index: int) -> Run:
|
| 60 |
+
self._load_runs()
|
| 61 |
+
return self._runs[index]
|
| 62 |
+
|
| 63 |
+
def __len__(self) -> int:
|
| 64 |
+
self._load_runs()
|
| 65 |
+
return len(self._runs)
|
| 66 |
+
|
| 67 |
+
def __repr__(self) -> str:
|
| 68 |
+
self._load_runs()
|
| 69 |
+
return f"<Runs project={self.project} count={len(self._runs)}>"
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class Api:
|
| 73 |
+
def runs(self, project: str) -> Runs:
|
| 74 |
+
if not SQLiteStorage.get_project_db_path(project).exists():
|
| 75 |
+
raise ValueError(f"Project '{project}' does not exist")
|
| 76 |
+
return Runs(project)
|
| 77 |
+
|
| 78 |
+
def alerts(
|
| 79 |
+
self,
|
| 80 |
+
project: str,
|
| 81 |
+
run: str | None = None,
|
| 82 |
+
level: str | None = None,
|
| 83 |
+
since: str | None = None,
|
| 84 |
+
) -> list[dict]:
|
| 85 |
+
if not SQLiteStorage.get_project_db_path(project).exists():
|
| 86 |
+
raise ValueError(f"Project '{project}' does not exist")
|
| 87 |
+
return SQLiteStorage.get_alerts(project, run_name=run, level=level, since=since)
|
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
|
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
|