braven 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.
@@ -0,0 +1,51 @@
1
+ name: Publish to PyPI
2
+
3
+ # Fires on a version tag push (vX.Y.Z). Uses PyPI's trusted-publisher (OIDC)
4
+ # flow — no long-lived API token stored as a repo secret. Before the first
5
+ # tag push, the `braven` project's trusted publisher must be registered on
6
+ # pypi.org via the "pending publisher" flow (the project doesn't exist on
7
+ # PyPI yet the first time this runs) — see .scratch/public-python-sdk/issues/06.
8
+
9
+ on:
10
+ push:
11
+ tags:
12
+ - "v*.*.*"
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+
25
+ - name: Install build tooling
26
+ run: pip install build
27
+
28
+ - name: Build sdist and wheel
29
+ run: python -m build
30
+
31
+ - name: Upload build artifacts
32
+ uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ publish:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment: pypi
41
+ permissions:
42
+ id-token: write # required for PyPI trusted-publisher OIDC
43
+ steps:
44
+ - name: Download build artifacts
45
+ uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,26 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ pytest:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+
22
+ - name: Install package + dev dependencies
23
+ run: pip install -e ".[dataframe,plots]" pytest
24
+
25
+ - name: Run pytest
26
+ run: pytest -v
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
braven-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Braven
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
braven-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: braven
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Braven experiment tracker
5
+ Project-URL: Homepage, https://github.com/bravenlab/braven-python
6
+ Project-URL: Issues, https://github.com/bravenlab/braven-python/issues
7
+ Project-URL: Documentation, https://docs.bravenlab.com
8
+ Author: Braven
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: requests>=2.28
13
+ Provides-Extra: dataframe
14
+ Requires-Dist: pandas>=1.5; extra == 'dataframe'
15
+ Provides-Extra: plots
16
+ Requires-Dist: matplotlib>=3.5; extra == 'plots'
17
+ Requires-Dist: pillow>=9.0; extra == 'plots'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # braven
21
+
22
+ Python SDK for [Braven](https://bravenlab.com), the experiment tracker for
23
+ hardware and photonics R&D teams.
24
+
25
+ ```bash
26
+ pip install braven
27
+ ```
28
+
29
+ ## Install and login
30
+
31
+ ```bash
32
+ pip install braven
33
+ python -m braven login
34
+ ```
35
+
36
+ `login` asks for your backend URL and an SDK API key (`braven_...`, from
37
+ your Braven account's Settings → Watcher Keys) and saves them to
38
+ `~/.braven/config.json`. Every script on the machine picks the credentials
39
+ up automatically — no need to pass them around.
40
+
41
+ ## Direct logging (wandb-style)
42
+
43
+ The common case: a script you run yourself, logging config, metrics, and
44
+ plots straight to Braven as it runs.
45
+
46
+ ```python
47
+ import braven
48
+
49
+ run = braven.init(name="My Experiment", company="My Company", project="My Project")
50
+
51
+ braven.config("radius_um", 55.0)
52
+ braven.config("gap_nm", 200.0)
53
+
54
+ braven.summary("q_factor", 18_400)
55
+ braven.summary("extinction_ratio_dB", 22.1)
56
+
57
+ braven.upload("spectrum.png")
58
+ braven.finish()
59
+ ```
60
+
61
+ `company`/`project` are only required if your API key can see more than one
62
+ project — pass them to disambiguate.
63
+
64
+ ## Matplotlib figures
65
+
66
+ Pass a live `matplotlib.figure.Figure` to `upload()` instead of a file path
67
+ and the SDK saves the PNG for you (same as `fig.savefig()` would) *and*
68
+ extracts an interactive companion plot from the figure's line/scatter data,
69
+ so the experiment page shows a zoomable, hoverable chart alongside the
70
+ static image — no extra API to learn:
71
+
72
+ ```python
73
+ import matplotlib.pyplot as plt
74
+
75
+ fig, ax = plt.subplots()
76
+ ax.plot(wavelengths_nm, through_db, label="Through port")
77
+ ax.set_xlabel("Wavelength (nm)")
78
+ ax.set_ylabel("Transmission (dB)")
79
+
80
+ braven.upload(fig, name="spectrum.png")
81
+ ```
82
+
83
+ This works the same way whether the figure came from a script you run
84
+ yourself or a script run through Braven's Pipelines tab — one code path, one
85
+ behavior, no separate API for "give me a nice list thumbnail."
86
+
87
+ Prefer to log a plain array without a matplotlib figure at all?
88
+ `plot_series()` does that directly:
89
+
90
+ ```python
91
+ braven.plot_series("snr_vs_temp", y=snr_values, x=temperatures, x_label="Temp (C)", y_label="SNR (dB)")
92
+ ```
93
+
94
+ ## Devices — multiple sensors/units in one experiment
95
+
96
+ Tag a value with a stable per-device key and the KPI name stays the same
97
+ across devices (no `SNR_dev1`, `SNR_dev2`) — the device becomes a separate
98
+ coordinate instead of a suffix:
99
+
100
+ ```python
101
+ for sensor_id, snr in results.items():
102
+ braven.device(sensor_id).log_summary("SNR", snr)
103
+
104
+ braven.log_summary("max_device_mismatch", spread) # experiment-level, all devices
105
+ ```
106
+
107
+ A device is auto-created on first sight and its history accumulates across
108
+ experiments — pass an optional type on first use
109
+ (`braven.device("SENSOR-4471", "Photodiode")`) to name what kind of device
110
+ it is; it's ignored once the device already exists.
111
+
112
+ ## Pipeline scripts (run by the Braven worker)
113
+
114
+ If your script runs through Braven's Pipelines tab (server-side, executed by
115
+ the Braven worker against uploaded files) rather than on your own machine,
116
+ the SDK dispatches to the same functions automatically — no `braven.init()`
117
+ call needed, and no credentials to manage (the worker supplies the
118
+ experiment context):
119
+
120
+ ```python
121
+ import braven
122
+
123
+ def process(braven=None):
124
+ braven.log_config("lr", "0.001")
125
+ braven.log_summary("acc", "0.94")
126
+ braven.log_artifact("plot.png")
127
+
128
+ # Devices and matplotlib figures work exactly as in direct logging:
129
+ braven.device("SENSOR-1").log_summary("SNR", 14.2)
130
+ braven.upload(fig, name="spectrum.png")
131
+ ```
132
+
133
+ `braven.files` (uploaded file → local temp path) and `braven.params`
134
+ (extracted parameters) are populated by the worker before `process()` runs.
135
+
136
+ ## Querying experiments
137
+
138
+ Read-only access to experiments already logged in Braven — no API key
139
+ required beyond what you already set up with `python -m braven login`, or
140
+ construct a client directly:
141
+
142
+ ```python
143
+ from braven import Braven
144
+
145
+ b = Braven("https://your-backend.example.com", api_key="braven_...")
146
+ exp = b.get("high temp run") # partial, case-insensitive name match
147
+ df = exp.file("data.csv").as_dataframe() # requires: pip install "braven[dataframe]"
148
+
149
+ print(exp.metadata) # {key: value} config/summary metadata
150
+ for f in exp.files:
151
+ print(f.filename)
152
+ ```
153
+
154
+ `b.get()` also accepts a list of names and returns a list of `Experiment`
155
+ objects. `b.experiments()` returns lightweight summaries of everything
156
+ visible to the API key.
157
+
158
+ ## Cross-experiment analysis
159
+
160
+ Pull an explicit, frozen set of experiments out of Braven for local work
161
+ (training a model, building a comparison plot — anything outside Braven),
162
+ then log results back onto the same object so the record of what was
163
+ analyzed can never drift from what was actually read:
164
+
165
+ ```python
166
+ analysis = braven.collect(
167
+ experiment_ids=["exp_1", "exp_2", "exp_3"],
168
+ name="Cross-lot yield comparison",
169
+ )
170
+
171
+ for exp in analysis.experiments():
172
+ df = exp.file("data.csv").as_dataframe()
173
+ # ... do local analysis ...
174
+
175
+ analysis.log_summary("mean_yield", 0.94)
176
+ analysis.upload(fig, name="comparison.png")
177
+ ```
178
+
179
+ `filter_token` (copied from Braven's webapp "Copy as code" action) works as
180
+ an alternative to an explicit `experiment_ids` list — it's re-resolved to
181
+ concrete experiment IDs at call time, never stored as a live query.
182
+
183
+ ## Optional dependencies
184
+
185
+ `requests` is the only hard dependency. Everything else is optional and
186
+ imported lazily:
187
+
188
+ | Feature | Install |
189
+ |---|---|
190
+ | `.as_dataframe()` on a downloaded file | `pip install "braven[dataframe]"` |
191
+ | Uploading a live matplotlib `Figure` (and its list thumbnail) | `pip install "braven[plots]"` |
192
+
193
+ ## Contributing / issues
194
+
195
+ Source: [github.com/bravenlab/braven-python](https://github.com/bravenlab/braven-python).
196
+ Found a bug or have a feature request? File it on
197
+ [GitHub Issues](https://github.com/bravenlab/braven-python/issues) — that's
198
+ the right place for anything specific to the SDK itself.
199
+
200
+ ## License
201
+
202
+ MIT — see [LICENSE](LICENSE).
braven-0.1.0/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # braven
2
+
3
+ Python SDK for [Braven](https://bravenlab.com), the experiment tracker for
4
+ hardware and photonics R&D teams.
5
+
6
+ ```bash
7
+ pip install braven
8
+ ```
9
+
10
+ ## Install and login
11
+
12
+ ```bash
13
+ pip install braven
14
+ python -m braven login
15
+ ```
16
+
17
+ `login` asks for your backend URL and an SDK API key (`braven_...`, from
18
+ your Braven account's Settings → Watcher Keys) and saves them to
19
+ `~/.braven/config.json`. Every script on the machine picks the credentials
20
+ up automatically — no need to pass them around.
21
+
22
+ ## Direct logging (wandb-style)
23
+
24
+ The common case: a script you run yourself, logging config, metrics, and
25
+ plots straight to Braven as it runs.
26
+
27
+ ```python
28
+ import braven
29
+
30
+ run = braven.init(name="My Experiment", company="My Company", project="My Project")
31
+
32
+ braven.config("radius_um", 55.0)
33
+ braven.config("gap_nm", 200.0)
34
+
35
+ braven.summary("q_factor", 18_400)
36
+ braven.summary("extinction_ratio_dB", 22.1)
37
+
38
+ braven.upload("spectrum.png")
39
+ braven.finish()
40
+ ```
41
+
42
+ `company`/`project` are only required if your API key can see more than one
43
+ project — pass them to disambiguate.
44
+
45
+ ## Matplotlib figures
46
+
47
+ Pass a live `matplotlib.figure.Figure` to `upload()` instead of a file path
48
+ and the SDK saves the PNG for you (same as `fig.savefig()` would) *and*
49
+ extracts an interactive companion plot from the figure's line/scatter data,
50
+ so the experiment page shows a zoomable, hoverable chart alongside the
51
+ static image — no extra API to learn:
52
+
53
+ ```python
54
+ import matplotlib.pyplot as plt
55
+
56
+ fig, ax = plt.subplots()
57
+ ax.plot(wavelengths_nm, through_db, label="Through port")
58
+ ax.set_xlabel("Wavelength (nm)")
59
+ ax.set_ylabel("Transmission (dB)")
60
+
61
+ braven.upload(fig, name="spectrum.png")
62
+ ```
63
+
64
+ This works the same way whether the figure came from a script you run
65
+ yourself or a script run through Braven's Pipelines tab — one code path, one
66
+ behavior, no separate API for "give me a nice list thumbnail."
67
+
68
+ Prefer to log a plain array without a matplotlib figure at all?
69
+ `plot_series()` does that directly:
70
+
71
+ ```python
72
+ braven.plot_series("snr_vs_temp", y=snr_values, x=temperatures, x_label="Temp (C)", y_label="SNR (dB)")
73
+ ```
74
+
75
+ ## Devices — multiple sensors/units in one experiment
76
+
77
+ Tag a value with a stable per-device key and the KPI name stays the same
78
+ across devices (no `SNR_dev1`, `SNR_dev2`) — the device becomes a separate
79
+ coordinate instead of a suffix:
80
+
81
+ ```python
82
+ for sensor_id, snr in results.items():
83
+ braven.device(sensor_id).log_summary("SNR", snr)
84
+
85
+ braven.log_summary("max_device_mismatch", spread) # experiment-level, all devices
86
+ ```
87
+
88
+ A device is auto-created on first sight and its history accumulates across
89
+ experiments — pass an optional type on first use
90
+ (`braven.device("SENSOR-4471", "Photodiode")`) to name what kind of device
91
+ it is; it's ignored once the device already exists.
92
+
93
+ ## Pipeline scripts (run by the Braven worker)
94
+
95
+ If your script runs through Braven's Pipelines tab (server-side, executed by
96
+ the Braven worker against uploaded files) rather than on your own machine,
97
+ the SDK dispatches to the same functions automatically — no `braven.init()`
98
+ call needed, and no credentials to manage (the worker supplies the
99
+ experiment context):
100
+
101
+ ```python
102
+ import braven
103
+
104
+ def process(braven=None):
105
+ braven.log_config("lr", "0.001")
106
+ braven.log_summary("acc", "0.94")
107
+ braven.log_artifact("plot.png")
108
+
109
+ # Devices and matplotlib figures work exactly as in direct logging:
110
+ braven.device("SENSOR-1").log_summary("SNR", 14.2)
111
+ braven.upload(fig, name="spectrum.png")
112
+ ```
113
+
114
+ `braven.files` (uploaded file → local temp path) and `braven.params`
115
+ (extracted parameters) are populated by the worker before `process()` runs.
116
+
117
+ ## Querying experiments
118
+
119
+ Read-only access to experiments already logged in Braven — no API key
120
+ required beyond what you already set up with `python -m braven login`, or
121
+ construct a client directly:
122
+
123
+ ```python
124
+ from braven import Braven
125
+
126
+ b = Braven("https://your-backend.example.com", api_key="braven_...")
127
+ exp = b.get("high temp run") # partial, case-insensitive name match
128
+ df = exp.file("data.csv").as_dataframe() # requires: pip install "braven[dataframe]"
129
+
130
+ print(exp.metadata) # {key: value} config/summary metadata
131
+ for f in exp.files:
132
+ print(f.filename)
133
+ ```
134
+
135
+ `b.get()` also accepts a list of names and returns a list of `Experiment`
136
+ objects. `b.experiments()` returns lightweight summaries of everything
137
+ visible to the API key.
138
+
139
+ ## Cross-experiment analysis
140
+
141
+ Pull an explicit, frozen set of experiments out of Braven for local work
142
+ (training a model, building a comparison plot — anything outside Braven),
143
+ then log results back onto the same object so the record of what was
144
+ analyzed can never drift from what was actually read:
145
+
146
+ ```python
147
+ analysis = braven.collect(
148
+ experiment_ids=["exp_1", "exp_2", "exp_3"],
149
+ name="Cross-lot yield comparison",
150
+ )
151
+
152
+ for exp in analysis.experiments():
153
+ df = exp.file("data.csv").as_dataframe()
154
+ # ... do local analysis ...
155
+
156
+ analysis.log_summary("mean_yield", 0.94)
157
+ analysis.upload(fig, name="comparison.png")
158
+ ```
159
+
160
+ `filter_token` (copied from Braven's webapp "Copy as code" action) works as
161
+ an alternative to an explicit `experiment_ids` list — it's re-resolved to
162
+ concrete experiment IDs at call time, never stored as a live query.
163
+
164
+ ## Optional dependencies
165
+
166
+ `requests` is the only hard dependency. Everything else is optional and
167
+ imported lazily:
168
+
169
+ | Feature | Install |
170
+ |---|---|
171
+ | `.as_dataframe()` on a downloaded file | `pip install "braven[dataframe]"` |
172
+ | Uploading a live matplotlib `Figure` (and its list thumbnail) | `pip install "braven[plots]"` |
173
+
174
+ ## Contributing / issues
175
+
176
+ Source: [github.com/bravenlab/braven-python](https://github.com/bravenlab/braven-python).
177
+ Found a bug or have a feature request? File it on
178
+ [GitHub Issues](https://github.com/bravenlab/braven-python/issues) — that's
179
+ the right place for anything specific to the SDK itself.
180
+
181
+ ## License
182
+
183
+ MIT — see [LICENSE](LICENSE).