openrunner-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openrunner_sdk-0.1.0/.gitignore +226 -0
- openrunner_sdk-0.1.0/=6.0 +5 -0
- openrunner_sdk-0.1.0/=8.1 +0 -0
- openrunner_sdk-0.1.0/PKG-INFO +91 -0
- openrunner_sdk-0.1.0/README.md +53 -0
- openrunner_sdk-0.1.0/openrunner/__init__.py +273 -0
- openrunner_sdk-0.1.0/openrunner/api_client.py +368 -0
- openrunner_sdk-0.1.0/openrunner/artifact.py +88 -0
- openrunner_sdk-0.1.0/openrunner/buffer.py +46 -0
- openrunner_sdk-0.1.0/openrunner/cache.py +92 -0
- openrunner_sdk-0.1.0/openrunner/cli.py +210 -0
- openrunner_sdk-0.1.0/openrunner/config.py +91 -0
- openrunner_sdk-0.1.0/openrunner/git_info.py +66 -0
- openrunner_sdk-0.1.0/openrunner/integration/__init__.py +2 -0
- openrunner_sdk-0.1.0/openrunner/integration/huggingface.py +83 -0
- openrunner_sdk-0.1.0/openrunner/integration/lightning.py +111 -0
- openrunner_sdk-0.1.0/openrunner/integration/pytorch.py +49 -0
- openrunner_sdk-0.1.0/openrunner/media.py +120 -0
- openrunner_sdk-0.1.0/openrunner/offline.py +259 -0
- openrunner_sdk-0.1.0/openrunner/run.py +399 -0
- openrunner_sdk-0.1.0/openrunner/sender.py +239 -0
- openrunner_sdk-0.1.0/openrunner/settings.py +105 -0
- openrunner_sdk-0.1.0/openrunner/summary.py +70 -0
- openrunner_sdk-0.1.0/openrunner/system_metrics.py +97 -0
- openrunner_sdk-0.1.0/openrunner/wandb_compat/__init__.py +25 -0
- openrunner_sdk-0.1.0/openrunner/wandb_compat/_shim.py +6 -0
- openrunner_sdk-0.1.0/pyproject.toml +44 -0
- openrunner_sdk-0.1.0/tests/__init__.py +0 -0
- openrunner_sdk-0.1.0/tests/conftest.py +79 -0
- openrunner_sdk-0.1.0/tests/test_api_client.py +162 -0
- openrunner_sdk-0.1.0/tests/test_artifact.py +111 -0
- openrunner_sdk-0.1.0/tests/test_buffer.py +115 -0
- openrunner_sdk-0.1.0/tests/test_cache.py +82 -0
- openrunner_sdk-0.1.0/tests/test_cli.py +204 -0
- openrunner_sdk-0.1.0/tests/test_config.py +136 -0
- openrunner_sdk-0.1.0/tests/test_finish.py +125 -0
- openrunner_sdk-0.1.0/tests/test_git_info.py +74 -0
- openrunner_sdk-0.1.0/tests/test_init.py +171 -0
- openrunner_sdk-0.1.0/tests/test_integration_huggingface.py +168 -0
- openrunner_sdk-0.1.0/tests/test_integration_lightning.py +185 -0
- openrunner_sdk-0.1.0/tests/test_integration_pytorch.py +123 -0
- openrunner_sdk-0.1.0/tests/test_log.py +159 -0
- openrunner_sdk-0.1.0/tests/test_media.py +127 -0
- openrunner_sdk-0.1.0/tests/test_offline.py +210 -0
- openrunner_sdk-0.1.0/tests/test_offline_sync.py +229 -0
- openrunner_sdk-0.1.0/tests/test_resume.py +257 -0
- openrunner_sdk-0.1.0/tests/test_sender.py +165 -0
- openrunner_sdk-0.1.0/tests/test_summary.py +95 -0
- openrunner_sdk-0.1.0/tests/test_system_metrics.py +138 -0
- openrunner_sdk-0.1.0/tests/test_wandb_compat.py +49 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
!src/web/src/lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py.cover
|
|
51
|
+
*.lcov
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
# Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
# poetry.lock
|
|
111
|
+
# poetry.toml
|
|
112
|
+
|
|
113
|
+
# pdm
|
|
114
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
115
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
116
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
117
|
+
# pdm.lock
|
|
118
|
+
# pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# pixi
|
|
123
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
124
|
+
# pixi.lock
|
|
125
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
126
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
127
|
+
.pixi/*
|
|
128
|
+
!.pixi/config.toml
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
celerybeat-schedule*
|
|
135
|
+
celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# Redis
|
|
138
|
+
*.rdb
|
|
139
|
+
*.aof
|
|
140
|
+
*.pid
|
|
141
|
+
|
|
142
|
+
# RabbitMQ
|
|
143
|
+
mnesia/
|
|
144
|
+
rabbitmq/
|
|
145
|
+
rabbitmq-data/
|
|
146
|
+
|
|
147
|
+
# ActiveMQ
|
|
148
|
+
activemq-data/
|
|
149
|
+
|
|
150
|
+
# SageMath parsed files
|
|
151
|
+
*.sage.py
|
|
152
|
+
|
|
153
|
+
# Environments
|
|
154
|
+
.env
|
|
155
|
+
.envrc
|
|
156
|
+
.venv
|
|
157
|
+
env/
|
|
158
|
+
venv/
|
|
159
|
+
ENV/
|
|
160
|
+
env.bak/
|
|
161
|
+
venv.bak/
|
|
162
|
+
|
|
163
|
+
# Spyder project settings
|
|
164
|
+
.spyderproject
|
|
165
|
+
.spyproject
|
|
166
|
+
|
|
167
|
+
# Rope project settings
|
|
168
|
+
.ropeproject
|
|
169
|
+
|
|
170
|
+
# mkdocs documentation
|
|
171
|
+
/site
|
|
172
|
+
|
|
173
|
+
# mypy
|
|
174
|
+
.mypy_cache/
|
|
175
|
+
.dmypy.json
|
|
176
|
+
dmypy.json
|
|
177
|
+
|
|
178
|
+
# Pyre type checker
|
|
179
|
+
.pyre/
|
|
180
|
+
|
|
181
|
+
# pytype static type analyzer
|
|
182
|
+
.pytype/
|
|
183
|
+
|
|
184
|
+
# Cython debug symbols
|
|
185
|
+
cython_debug/
|
|
186
|
+
|
|
187
|
+
# PyCharm
|
|
188
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
189
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
190
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
191
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
192
|
+
# .idea/
|
|
193
|
+
|
|
194
|
+
# Abstra
|
|
195
|
+
# Abstra is an AI-powered process automation framework.
|
|
196
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
197
|
+
# Learn more at https://abstra.io/docs
|
|
198
|
+
.abstra/
|
|
199
|
+
|
|
200
|
+
# Visual Studio Code
|
|
201
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
202
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
203
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
204
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
205
|
+
# .vscode/
|
|
206
|
+
# Temporary file for partial code execution
|
|
207
|
+
tempCodeRunnerFile.py
|
|
208
|
+
|
|
209
|
+
# Node.js
|
|
210
|
+
node_modules/
|
|
211
|
+
*.tsbuildinfo
|
|
212
|
+
|
|
213
|
+
# Ruff stuff:
|
|
214
|
+
.ruff_cache/
|
|
215
|
+
|
|
216
|
+
# PyPI configuration file
|
|
217
|
+
.pypirc
|
|
218
|
+
|
|
219
|
+
# Marimo
|
|
220
|
+
marimo/_static/
|
|
221
|
+
marimo/_lsp/
|
|
222
|
+
__marimo__/
|
|
223
|
+
|
|
224
|
+
# Streamlit
|
|
225
|
+
.streamlit/secrets.toml
|
|
226
|
+
data/
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Collecting psutil
|
|
2
|
+
Using cached psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl.metadata (22 kB)
|
|
3
|
+
Using cached psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl (155 kB)
|
|
4
|
+
Installing collected packages: psutil
|
|
5
|
+
Successfully installed psutil-7.2.2
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openrunner-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
|
|
5
|
+
Project-URL: Homepage, https://github.com/jqueguiner/openrunner
|
|
6
|
+
Project-URL: Repository, https://github.com/jqueguiner/openrunner
|
|
7
|
+
Project-URL: Issues, https://github.com/jqueguiner/openrunner/issues
|
|
8
|
+
Author-email: JL Queguiner <jl@gladia.io>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: experiment-tracking,machine-learning,ml,mlops,wandb
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pillow>=10.0
|
|
25
|
+
Requires-Dist: psutil>=6.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Provides-Extra: gpu
|
|
30
|
+
Requires-Dist: nvidia-ml-py>=12.0; extra == 'gpu'
|
|
31
|
+
Provides-Extra: huggingface
|
|
32
|
+
Requires-Dist: transformers>=4.30; extra == 'huggingface'
|
|
33
|
+
Provides-Extra: lightning
|
|
34
|
+
Requires-Dist: lightning>=2.0; extra == 'lightning'
|
|
35
|
+
Provides-Extra: pytorch
|
|
36
|
+
Requires-Dist: torch>=2.0; extra == 'pytorch'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# OpenRunner SDK
|
|
40
|
+
|
|
41
|
+
Open-source, self-hosted ML experiment tracking — a drop-in replacement for Weights & Biases.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install openrunner-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export OPENRUNNER_API_KEY="or_your_key"
|
|
53
|
+
export OPENRUNNER_BASE_URL="https://your-server.com"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import openrunner
|
|
58
|
+
|
|
59
|
+
openrunner.init(project="my-project", config={"lr": 0.001})
|
|
60
|
+
|
|
61
|
+
for epoch in range(10):
|
|
62
|
+
loss = train(epoch)
|
|
63
|
+
openrunner.log({"loss": loss, "epoch": epoch})
|
|
64
|
+
|
|
65
|
+
openrunner.finish()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Migrating from W&B
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import openrunner as wandb
|
|
72
|
+
|
|
73
|
+
wandb.init(project="my-project")
|
|
74
|
+
wandb.log({"loss": 0.5})
|
|
75
|
+
wandb.finish()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Features
|
|
79
|
+
|
|
80
|
+
- **W&B-compatible API** — `init()`, `log()`, `finish()`, `config`, `summary`
|
|
81
|
+
- **Non-blocking** — logging never slows down training
|
|
82
|
+
- **Artifacts** — version datasets, models, checkpoints with SHA-256 dedup
|
|
83
|
+
- **Media** — log images (`Image()`) and tables (`Table()`)
|
|
84
|
+
- **Offline mode** — works without connectivity, sync later
|
|
85
|
+
- **Framework integrations** — PyTorch, HuggingFace, Lightning
|
|
86
|
+
- **CLI** — `openrunner login`, `openrunner sync`, `openrunner ls`
|
|
87
|
+
- **System metrics** — GPU/CPU/memory monitoring via psutil
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# OpenRunner SDK
|
|
2
|
+
|
|
3
|
+
Open-source, self-hosted ML experiment tracking — a drop-in replacement for Weights & Biases.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install openrunner-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export OPENRUNNER_API_KEY="or_your_key"
|
|
15
|
+
export OPENRUNNER_BASE_URL="https://your-server.com"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import openrunner
|
|
20
|
+
|
|
21
|
+
openrunner.init(project="my-project", config={"lr": 0.001})
|
|
22
|
+
|
|
23
|
+
for epoch in range(10):
|
|
24
|
+
loss = train(epoch)
|
|
25
|
+
openrunner.log({"loss": loss, "epoch": epoch})
|
|
26
|
+
|
|
27
|
+
openrunner.finish()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Migrating from W&B
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import openrunner as wandb
|
|
34
|
+
|
|
35
|
+
wandb.init(project="my-project")
|
|
36
|
+
wandb.log({"loss": 0.5})
|
|
37
|
+
wandb.finish()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **W&B-compatible API** — `init()`, `log()`, `finish()`, `config`, `summary`
|
|
43
|
+
- **Non-blocking** — logging never slows down training
|
|
44
|
+
- **Artifacts** — version datasets, models, checkpoints with SHA-256 dedup
|
|
45
|
+
- **Media** — log images (`Image()`) and tables (`Table()`)
|
|
46
|
+
- **Offline mode** — works without connectivity, sync later
|
|
47
|
+
- **Framework integrations** — PyTorch, HuggingFace, Lightning
|
|
48
|
+
- **CLI** — `openrunner login`, `openrunner sync`, `openrunner ls`
|
|
49
|
+
- **System metrics** — GPU/CPU/memory monitoring via psutil
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""OpenRunner SDK - W&B-compatible ML experiment tracking.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
openrunner.init(project=..., name=..., config=...) -> Run
|
|
5
|
+
openrunner.log({"loss": 0.5}, step=10) -> None
|
|
6
|
+
openrunner.finish(exit_code=0) -> None
|
|
7
|
+
openrunner.config -> Config proxy
|
|
8
|
+
openrunner.summary -> Summary proxy
|
|
9
|
+
openrunner.run -> active Run or None
|
|
10
|
+
openrunner.Image -> Image class for media logging
|
|
11
|
+
openrunner.Table -> Table class for structured data
|
|
12
|
+
openrunner.Artifact -> Artifact class for versioned file collections
|
|
13
|
+
openrunner.log_artifact -> Upload an artifact
|
|
14
|
+
openrunner.use_artifact -> Download an artifact (cached)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from openrunner.artifact import Artifact
|
|
24
|
+
from openrunner.config import Config
|
|
25
|
+
from openrunner.media import Image, Table
|
|
26
|
+
from openrunner.run import Run
|
|
27
|
+
from openrunner.settings import SDKSettings
|
|
28
|
+
from openrunner.summary import Summary
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger("openrunner")
|
|
33
|
+
|
|
34
|
+
# Active run state
|
|
35
|
+
_active_run: Run | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Proxy objects for module-level config/summary access
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
class _ConfigProxy:
|
|
43
|
+
"""Proxy that delegates attribute access to the active run's Config.
|
|
44
|
+
|
|
45
|
+
Since Python modules can't have properties, this proxy object sits
|
|
46
|
+
at ``openrunner.config`` and forwards all access to ``_active_run.config``.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __getattr__(self, name: str) -> Any:
|
|
50
|
+
if _active_run is not None:
|
|
51
|
+
return getattr(_active_run.config, name)
|
|
52
|
+
raise AttributeError(f"No active run -- call openrunner.init() first")
|
|
53
|
+
|
|
54
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
55
|
+
if _active_run is not None:
|
|
56
|
+
setattr(_active_run.config, name, value)
|
|
57
|
+
else:
|
|
58
|
+
logger.warning("openrunner.config: no active run -- call openrunner.init() first")
|
|
59
|
+
|
|
60
|
+
def __getitem__(self, key: str) -> Any:
|
|
61
|
+
if _active_run is not None:
|
|
62
|
+
return _active_run.config[key]
|
|
63
|
+
raise KeyError(f"No active run -- call openrunner.init() first")
|
|
64
|
+
|
|
65
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
66
|
+
if _active_run is not None:
|
|
67
|
+
_active_run.config[key] = value
|
|
68
|
+
else:
|
|
69
|
+
logger.warning("openrunner.config: no active run -- call openrunner.init() first")
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
if _active_run is not None:
|
|
73
|
+
return repr(_active_run.config)
|
|
74
|
+
return "ConfigProxy(no active run)"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class _SummaryProxy:
|
|
78
|
+
"""Proxy that delegates attribute access to the active run's Summary."""
|
|
79
|
+
|
|
80
|
+
def __getattr__(self, name: str) -> Any:
|
|
81
|
+
if _active_run is not None:
|
|
82
|
+
return getattr(_active_run.summary, name)
|
|
83
|
+
raise AttributeError(f"No active run -- call openrunner.init() first")
|
|
84
|
+
|
|
85
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
86
|
+
if _active_run is not None:
|
|
87
|
+
setattr(_active_run.summary, name, value)
|
|
88
|
+
else:
|
|
89
|
+
logger.warning("openrunner.summary: no active run -- call openrunner.init() first")
|
|
90
|
+
|
|
91
|
+
def __getitem__(self, key: str) -> Any:
|
|
92
|
+
if _active_run is not None:
|
|
93
|
+
return _active_run.summary[key]
|
|
94
|
+
raise KeyError(f"No active run -- call openrunner.init() first")
|
|
95
|
+
|
|
96
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
97
|
+
if _active_run is not None:
|
|
98
|
+
_active_run.summary[key] = value
|
|
99
|
+
else:
|
|
100
|
+
logger.warning("openrunner.summary: no active run -- call openrunner.init() first")
|
|
101
|
+
|
|
102
|
+
def __repr__(self) -> str:
|
|
103
|
+
if _active_run is not None:
|
|
104
|
+
return repr(_active_run.summary)
|
|
105
|
+
return "SummaryProxy(no active run)"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Module-level proxy instances
|
|
109
|
+
config = _ConfigProxy()
|
|
110
|
+
summary = _SummaryProxy()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Public API functions
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def init(
|
|
118
|
+
project: str | None = None,
|
|
119
|
+
name: str | None = None,
|
|
120
|
+
config: dict[str, Any] | None = None,
|
|
121
|
+
tags: list[str] | None = None,
|
|
122
|
+
notes: str | None = None,
|
|
123
|
+
group: str | None = None,
|
|
124
|
+
job_type: str | None = None,
|
|
125
|
+
id: str | None = None,
|
|
126
|
+
resume: bool | str | None = None,
|
|
127
|
+
**kwargs: Any,
|
|
128
|
+
) -> Run | None:
|
|
129
|
+
"""Initialize a new run.
|
|
130
|
+
|
|
131
|
+
Creates a run on the server, starts the background sender thread,
|
|
132
|
+
and returns the Run object. Never raises -- SDK failures must not
|
|
133
|
+
crash training code.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
project: Project name (or OPENRUNNER_PROJECT env var, or "uncategorized").
|
|
137
|
+
name: Display name for the run.
|
|
138
|
+
config: Hyperparameter dict (sent to server at init time, SDK-12).
|
|
139
|
+
tags: List of tags for the run.
|
|
140
|
+
notes: Notes/description for the run.
|
|
141
|
+
group: Group name for related runs.
|
|
142
|
+
job_type: Job type label.
|
|
143
|
+
id: Custom run ID (8-char alphanumeric generated if not provided).
|
|
144
|
+
resume: Resume mode -- True/"allow" (resume if exists, fresh otherwise)
|
|
145
|
+
or "must" (error if parent not found). The ``id`` parameter
|
|
146
|
+
is treated as the parent run ID when resuming.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The Run object, or None if initialization fails.
|
|
150
|
+
"""
|
|
151
|
+
global _active_run
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
settings = SDKSettings()
|
|
155
|
+
|
|
156
|
+
# Resolve project
|
|
157
|
+
resolved_project = project or settings.project or "uncategorized"
|
|
158
|
+
|
|
159
|
+
# Warn if no API key (skip in offline mode)
|
|
160
|
+
if not settings.api_key and settings.mode != "offline":
|
|
161
|
+
logger.warning(
|
|
162
|
+
"No API key set. Set OPENRUNNER_API_KEY or WANDB_API_KEY "
|
|
163
|
+
"environment variable for server communication."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Create run
|
|
167
|
+
run = Run(
|
|
168
|
+
project=resolved_project,
|
|
169
|
+
name=name,
|
|
170
|
+
config_dict=config,
|
|
171
|
+
tags=tags,
|
|
172
|
+
notes=notes,
|
|
173
|
+
group=group,
|
|
174
|
+
job_type=job_type,
|
|
175
|
+
run_id=id,
|
|
176
|
+
settings=settings,
|
|
177
|
+
resume=resume,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
_active_run = run
|
|
181
|
+
return run
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.warning("openrunner.init() failed: %s", e)
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def log(
|
|
189
|
+
data: dict[str, Any],
|
|
190
|
+
step: int | None = None,
|
|
191
|
+
commit: bool = True,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Log metrics. Never raises -- training must not be interrupted.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
data: Dict of metric key-value pairs.
|
|
197
|
+
step: Explicit step value.
|
|
198
|
+
commit: If True (default), finalize the current step.
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
if _active_run is None:
|
|
202
|
+
logger.warning("openrunner.log(): no active run -- call openrunner.init() first")
|
|
203
|
+
return
|
|
204
|
+
_active_run.log(data, step=step, commit=commit)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.warning("openrunner.log() failed: %s", e)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def finish(
|
|
210
|
+
exit_code: int | None = None,
|
|
211
|
+
quiet: bool | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Finish the active run. Never raises.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
exit_code: Optional exit code.
|
|
217
|
+
quiet: If True, suppress the finish message.
|
|
218
|
+
"""
|
|
219
|
+
global _active_run
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
if _active_run is None:
|
|
223
|
+
return
|
|
224
|
+
_active_run.finish(exit_code=exit_code, quiet=quiet or False)
|
|
225
|
+
_active_run = None
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.warning("openrunner.finish() failed: %s", e)
|
|
228
|
+
_active_run = None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
|
|
232
|
+
"""Upload an artifact to the active run. Never raises.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
artifact: The Artifact object with files added via add_file/add_dir.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Server response dict with version info, or None on failure.
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
if _active_run is None:
|
|
242
|
+
logger.warning("openrunner.log_artifact(): no active run")
|
|
243
|
+
return None
|
|
244
|
+
return _active_run.log_artifact(artifact)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.warning("openrunner.log_artifact() failed: %s", e)
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def use_artifact(name: str, version: int | None = None) -> Path | None:
|
|
251
|
+
"""Download an artifact and cache locally. Never raises.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
name: Artifact name.
|
|
255
|
+
version: Specific version number, or None for latest.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Path to the local artifact directory, or None on failure.
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
if _active_run is None:
|
|
262
|
+
logger.warning("openrunner.use_artifact(): no active run")
|
|
263
|
+
return None
|
|
264
|
+
return _active_run.use_artifact(name, version)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.warning("openrunner.use_artifact() failed: %s", e)
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@property # type: ignore[misc]
|
|
271
|
+
def run() -> Run | None:
|
|
272
|
+
"""Return the active run, or None."""
|
|
273
|
+
return _active_run
|