biocompute 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.
- biocompute-0.1.0/.gitignore +237 -0
- biocompute-0.1.0/PKG-INFO +118 -0
- biocompute-0.1.0/README.md +106 -0
- biocompute-0.1.0/_leakage.py +61 -0
- biocompute-0.1.0/check_leakage.py +38 -0
- biocompute-0.1.0/hatch_build.py +34 -0
- biocompute-0.1.0/pyproject.toml +45 -0
- biocompute-0.1.0/src/biocompute/__init__.py +22 -0
- biocompute-0.1.0/src/biocompute/_version.py +1 -0
- biocompute-0.1.0/src/biocompute/competition.py +198 -0
- biocompute-0.1.0/src/biocompute/exceptions.py +7 -0
- biocompute-0.1.0/src/biocompute/ops.py +46 -0
- biocompute-0.1.0/src/biocompute/py.typed +0 -0
- biocompute-0.1.0/src/biocompute/reagent.py +25 -0
- biocompute-0.1.0/src/biocompute/trace.py +39 -0
- biocompute-0.1.0/src/biocompute/well.py +76 -0
- biocompute-0.1.0/tests/__init__.py +0 -0
- biocompute-0.1.0/tests/conftest.py +16 -0
- biocompute-0.1.0/tests/test_competition.py +95 -0
- biocompute-0.1.0/tests/test_ops.py +32 -0
- biocompute-0.1.0/tests/test_well.py +100 -0
- biocompute-0.1.0/uv.lock +308 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
controller/vision/eval
|
|
3
|
+
controller/vision/models/
|
|
4
|
+
controller/vision/datasets/
|
|
5
|
+
easyeda-converter
|
|
6
|
+
.env
|
|
7
|
+
venv
|
|
8
|
+
env
|
|
9
|
+
_tmp_pbr_mesh.ply
|
|
10
|
+
_tmp_pbr_mesh.ply
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*$py.class
|
|
14
|
+
|
|
15
|
+
# C extensions
|
|
16
|
+
*.so
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
.Python
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib64/
|
|
27
|
+
parts/
|
|
28
|
+
sdist/
|
|
29
|
+
var/
|
|
30
|
+
wheels/
|
|
31
|
+
share/python-wheels/
|
|
32
|
+
*.egg-info/
|
|
33
|
+
.installed.cfg
|
|
34
|
+
*.egg
|
|
35
|
+
MANIFEST
|
|
36
|
+
|
|
37
|
+
# PyInstaller
|
|
38
|
+
# Usually these files are written by a python script from a template
|
|
39
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
40
|
+
*.manifest
|
|
41
|
+
*.spec
|
|
42
|
+
|
|
43
|
+
# Installer logs
|
|
44
|
+
pip-log.txt
|
|
45
|
+
pip-delete-this-directory.txt
|
|
46
|
+
|
|
47
|
+
# Unit test / coverage reports
|
|
48
|
+
htmlcov/
|
|
49
|
+
.tox/
|
|
50
|
+
.nox/
|
|
51
|
+
.coverage
|
|
52
|
+
.coverage.*
|
|
53
|
+
.cache
|
|
54
|
+
nosetests.xml
|
|
55
|
+
coverage.xml
|
|
56
|
+
*.cover
|
|
57
|
+
*.py,cover
|
|
58
|
+
.hypothesis/
|
|
59
|
+
.pytest_cache/
|
|
60
|
+
cover/
|
|
61
|
+
|
|
62
|
+
# Translations
|
|
63
|
+
*.mo
|
|
64
|
+
*.pot
|
|
65
|
+
|
|
66
|
+
# Django stuff:
|
|
67
|
+
*.log
|
|
68
|
+
local_settings.py
|
|
69
|
+
db.sqlite3
|
|
70
|
+
db.sqlite3-journal
|
|
71
|
+
|
|
72
|
+
# Flask stuff:
|
|
73
|
+
instance/
|
|
74
|
+
.webassets-cache
|
|
75
|
+
|
|
76
|
+
# Scrapy stuff:
|
|
77
|
+
.scrapy
|
|
78
|
+
|
|
79
|
+
# Sphinx documentation
|
|
80
|
+
docs/_build/
|
|
81
|
+
|
|
82
|
+
# PyBuilder
|
|
83
|
+
.pybuilder/
|
|
84
|
+
target/
|
|
85
|
+
|
|
86
|
+
# Jupyter Notebook
|
|
87
|
+
.ipynb_checkpoints
|
|
88
|
+
|
|
89
|
+
# IPython
|
|
90
|
+
profile_default/
|
|
91
|
+
ipython_config.py
|
|
92
|
+
|
|
93
|
+
# pyenv
|
|
94
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
95
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
96
|
+
# .python-version
|
|
97
|
+
|
|
98
|
+
# pipenv
|
|
99
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
100
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
101
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
102
|
+
# install all needed dependencies.
|
|
103
|
+
#Pipfile.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
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
#pdm.lock
|
|
115
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
116
|
+
# in version control.
|
|
117
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
118
|
+
.pdm.toml
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
|
166
|
+
# project, it is recommended to ignore all files in the .idea directory except workspace.xml and tasks.xml.
|
|
167
|
+
.idea/
|
|
168
|
+
*.iws
|
|
169
|
+
*.iml
|
|
170
|
+
*.ipr
|
|
171
|
+
|
|
172
|
+
# Visual Studio Code
|
|
173
|
+
.vscode/
|
|
174
|
+
|
|
175
|
+
# macOS
|
|
176
|
+
.DS_Store
|
|
177
|
+
.AppleDouble
|
|
178
|
+
.LSOverride
|
|
179
|
+
|
|
180
|
+
# Media files
|
|
181
|
+
*.mp4
|
|
182
|
+
*.avi
|
|
183
|
+
*.mkv
|
|
184
|
+
*.mov
|
|
185
|
+
*.wmv
|
|
186
|
+
*.flv
|
|
187
|
+
*.webm
|
|
188
|
+
*.m4v
|
|
189
|
+
*.3gp
|
|
190
|
+
*.mp3
|
|
191
|
+
*.wav
|
|
192
|
+
*.flac
|
|
193
|
+
*.aac
|
|
194
|
+
*.ogg
|
|
195
|
+
*.wma
|
|
196
|
+
*.m4a
|
|
197
|
+
*.jpg
|
|
198
|
+
*.jpeg
|
|
199
|
+
*.gif
|
|
200
|
+
*.bmp
|
|
201
|
+
*.tiff
|
|
202
|
+
*.webp
|
|
203
|
+
*.svg
|
|
204
|
+
*.ico
|
|
205
|
+
|
|
206
|
+
# Temporary files
|
|
207
|
+
*.tmp
|
|
208
|
+
*.temp
|
|
209
|
+
*~
|
|
210
|
+
|
|
211
|
+
# Node.js / JavaScript / TypeScript
|
|
212
|
+
node_modules/
|
|
213
|
+
npm-debug.log*
|
|
214
|
+
yarn-debug.log*
|
|
215
|
+
yarn-error.log*
|
|
216
|
+
.pnpm-debug.log*
|
|
217
|
+
.npm
|
|
218
|
+
.env
|
|
219
|
+
.env.development.local
|
|
220
|
+
.env.test.local
|
|
221
|
+
.env.production.local
|
|
222
|
+
.env.local
|
|
223
|
+
*.tsbuildinfo
|
|
224
|
+
.next
|
|
225
|
+
.nuxt
|
|
226
|
+
.vuepress/dist
|
|
227
|
+
.docusaurus
|
|
228
|
+
.serverless/
|
|
229
|
+
.fusebox/
|
|
230
|
+
.dynamodb/
|
|
231
|
+
.tern-port
|
|
232
|
+
dist/
|
|
233
|
+
coverage/
|
|
234
|
+
runs/
|
|
235
|
+
|
|
236
|
+
# PlatformIO
|
|
237
|
+
.pio/
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: biocompute
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: London Biocompute client library
|
|
5
|
+
Author: London Biocompute
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# biocompute
|
|
14
|
+
|
|
15
|
+
London Biocompute client library.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install biocompute
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from biocompute import Competition, wells, red_dye, green_dye, blue_dye
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
with Competition(api_key="sk_...", base_url="https://...") as comp:
|
|
30
|
+
|
|
31
|
+
# Experiments are just functions
|
|
32
|
+
def measure_color(well, r, g, b):
|
|
33
|
+
well.fill(vol=r, reagent=red_dye)
|
|
34
|
+
well.fill(vol=g, reagent=green_dye)
|
|
35
|
+
well.fill(vol=b, reagent=blue_dye)
|
|
36
|
+
well.mix()
|
|
37
|
+
well.image()
|
|
38
|
+
|
|
39
|
+
# Explore: grid search
|
|
40
|
+
n_wells = 25
|
|
41
|
+
explore = np.linspace([10, 10, 10], [100, 100, 100], num=n_wells)
|
|
42
|
+
|
|
43
|
+
for well, (r, g, b) in zip(wells(count=n_wells), explore):
|
|
44
|
+
measure_color(well, r, g, b)
|
|
45
|
+
|
|
46
|
+
results = comp.submit()
|
|
47
|
+
|
|
48
|
+
# Find best well
|
|
49
|
+
best_idx = max(range(len(results.wells)), key=lambda i: results.wells[i].score or 0)
|
|
50
|
+
best_params = explore[best_idx]
|
|
51
|
+
|
|
52
|
+
with Competition(api_key="sk_...", base_url="https://...") as comp:
|
|
53
|
+
|
|
54
|
+
# Exploit: random samples around best hit
|
|
55
|
+
exploit = np.random.normal(best_params, scale=5, size=(n_wells, 3)).clip(0, 200)
|
|
56
|
+
|
|
57
|
+
for well, (r, g, b) in zip(wells(count=n_wells), exploit):
|
|
58
|
+
measure_color(well, r, g, b)
|
|
59
|
+
|
|
60
|
+
results = comp.submit()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`wells(count=n)` gives you `n` wells. Pair with any sampling strategy via `zip`.
|
|
64
|
+
|
|
65
|
+
Use `numpy`, `scipy.stats.qmc`, `pyDOE`, Ax, or any ML model to generate parameter arrays. The system doesn't care — it just sees wells and function calls.
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### `Competition(api_key, *, challenge_id="default", base_url, timeout=300.0)`
|
|
70
|
+
|
|
71
|
+
Creates a competition session. Use as a context manager to ensure cleanup.
|
|
72
|
+
|
|
73
|
+
- `submit()` — serialize recorded ops, POST to server, poll for results
|
|
74
|
+
- `target()` — get the target image URL for this challenge
|
|
75
|
+
- `leaderboard()` — get the public leaderboard
|
|
76
|
+
- `close()` — release resources (called automatically by context manager)
|
|
77
|
+
|
|
78
|
+
### `wells(count=96)`
|
|
79
|
+
|
|
80
|
+
Generator that yields `Well` objects. Must be called after creating a `Competition`.
|
|
81
|
+
|
|
82
|
+
### `Well`
|
|
83
|
+
|
|
84
|
+
- `fill(vol, reagent)` — fill with a volume (microliters) of reagent
|
|
85
|
+
- `mix()` — mix well contents
|
|
86
|
+
- `image()` — capture an image of the well
|
|
87
|
+
|
|
88
|
+
All methods return `self` for chaining: `well.fill(vol=50.0, reagent=red_dye).mix().image()`
|
|
89
|
+
|
|
90
|
+
### Reagents
|
|
91
|
+
|
|
92
|
+
Built-in: `red_dye`, `green_dye`, `blue_dye`, `water`
|
|
93
|
+
|
|
94
|
+
Custom: `Reagent("my_reagent")`
|
|
95
|
+
|
|
96
|
+
## Build & Publish
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd biocompute
|
|
100
|
+
|
|
101
|
+
# Install dev dependencies
|
|
102
|
+
uv sync
|
|
103
|
+
|
|
104
|
+
# Run tests
|
|
105
|
+
uv run pytest
|
|
106
|
+
|
|
107
|
+
# Lint & type check
|
|
108
|
+
uv run ruff check .
|
|
109
|
+
uv run mypy src/biocompute
|
|
110
|
+
|
|
111
|
+
# Build (leakage check runs automatically)
|
|
112
|
+
uv run python -m build
|
|
113
|
+
|
|
114
|
+
# Manual leakage check
|
|
115
|
+
python check_leakage.py
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The build includes an automatic leakage check (`hatch_build.py`) that scans for references to private internal modules and aborts if any are found.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# biocompute
|
|
2
|
+
|
|
3
|
+
London Biocompute client library.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install biocompute
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from biocompute import Competition, wells, red_dye, green_dye, blue_dye
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
with Competition(api_key="sk_...", base_url="https://...") as comp:
|
|
18
|
+
|
|
19
|
+
# Experiments are just functions
|
|
20
|
+
def measure_color(well, r, g, b):
|
|
21
|
+
well.fill(vol=r, reagent=red_dye)
|
|
22
|
+
well.fill(vol=g, reagent=green_dye)
|
|
23
|
+
well.fill(vol=b, reagent=blue_dye)
|
|
24
|
+
well.mix()
|
|
25
|
+
well.image()
|
|
26
|
+
|
|
27
|
+
# Explore: grid search
|
|
28
|
+
n_wells = 25
|
|
29
|
+
explore = np.linspace([10, 10, 10], [100, 100, 100], num=n_wells)
|
|
30
|
+
|
|
31
|
+
for well, (r, g, b) in zip(wells(count=n_wells), explore):
|
|
32
|
+
measure_color(well, r, g, b)
|
|
33
|
+
|
|
34
|
+
results = comp.submit()
|
|
35
|
+
|
|
36
|
+
# Find best well
|
|
37
|
+
best_idx = max(range(len(results.wells)), key=lambda i: results.wells[i].score or 0)
|
|
38
|
+
best_params = explore[best_idx]
|
|
39
|
+
|
|
40
|
+
with Competition(api_key="sk_...", base_url="https://...") as comp:
|
|
41
|
+
|
|
42
|
+
# Exploit: random samples around best hit
|
|
43
|
+
exploit = np.random.normal(best_params, scale=5, size=(n_wells, 3)).clip(0, 200)
|
|
44
|
+
|
|
45
|
+
for well, (r, g, b) in zip(wells(count=n_wells), exploit):
|
|
46
|
+
measure_color(well, r, g, b)
|
|
47
|
+
|
|
48
|
+
results = comp.submit()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`wells(count=n)` gives you `n` wells. Pair with any sampling strategy via `zip`.
|
|
52
|
+
|
|
53
|
+
Use `numpy`, `scipy.stats.qmc`, `pyDOE`, Ax, or any ML model to generate parameter arrays. The system doesn't care — it just sees wells and function calls.
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### `Competition(api_key, *, challenge_id="default", base_url, timeout=300.0)`
|
|
58
|
+
|
|
59
|
+
Creates a competition session. Use as a context manager to ensure cleanup.
|
|
60
|
+
|
|
61
|
+
- `submit()` — serialize recorded ops, POST to server, poll for results
|
|
62
|
+
- `target()` — get the target image URL for this challenge
|
|
63
|
+
- `leaderboard()` — get the public leaderboard
|
|
64
|
+
- `close()` — release resources (called automatically by context manager)
|
|
65
|
+
|
|
66
|
+
### `wells(count=96)`
|
|
67
|
+
|
|
68
|
+
Generator that yields `Well` objects. Must be called after creating a `Competition`.
|
|
69
|
+
|
|
70
|
+
### `Well`
|
|
71
|
+
|
|
72
|
+
- `fill(vol, reagent)` — fill with a volume (microliters) of reagent
|
|
73
|
+
- `mix()` — mix well contents
|
|
74
|
+
- `image()` — capture an image of the well
|
|
75
|
+
|
|
76
|
+
All methods return `self` for chaining: `well.fill(vol=50.0, reagent=red_dye).mix().image()`
|
|
77
|
+
|
|
78
|
+
### Reagents
|
|
79
|
+
|
|
80
|
+
Built-in: `red_dye`, `green_dye`, `blue_dye`, `water`
|
|
81
|
+
|
|
82
|
+
Custom: `Reagent("my_reagent")`
|
|
83
|
+
|
|
84
|
+
## Build & Publish
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cd biocompute
|
|
88
|
+
|
|
89
|
+
# Install dev dependencies
|
|
90
|
+
uv sync
|
|
91
|
+
|
|
92
|
+
# Run tests
|
|
93
|
+
uv run pytest
|
|
94
|
+
|
|
95
|
+
# Lint & type check
|
|
96
|
+
uv run ruff check .
|
|
97
|
+
uv run mypy src/biocompute
|
|
98
|
+
|
|
99
|
+
# Build (leakage check runs automatically)
|
|
100
|
+
uv run python -m build
|
|
101
|
+
|
|
102
|
+
# Manual leakage check
|
|
103
|
+
python check_leakage.py
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The build includes an automatic leakage check (`hatch_build.py`) that scans for references to private internal modules and aborts if any are found.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Leakage detection for the biocompute package.
|
|
2
|
+
|
|
3
|
+
Scans source files for references to private internal modules
|
|
4
|
+
(controller.*, compiler types, hardware details) that should
|
|
5
|
+
never ship in the public package.
|
|
6
|
+
|
|
7
|
+
Used by:
|
|
8
|
+
- hatch_build.py (build hook, runs automatically on build)
|
|
9
|
+
- check_leakage.py (CLI, for manual checks)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Patterns that indicate leakage of private internals
|
|
18
|
+
FORBIDDEN = [
|
|
19
|
+
(r"\bfrom\s+controller\b", "import from controller (private)"),
|
|
20
|
+
(r"\bimport\s+controller\b", "import of controller (private)"),
|
|
21
|
+
(r"\bUOp\b", "reference to internal UOp type"),
|
|
22
|
+
(r"\bSourceLocation\b", "reference to internal SourceLocation"),
|
|
23
|
+
(r"\bcompile_protocol\b", "reference to compile_protocol"),
|
|
24
|
+
(r"\bCodeGenerator\b", "reference to internal CodeGenerator"),
|
|
25
|
+
(r"\bDependencyDAG\b", "reference to internal DependencyDAG"),
|
|
26
|
+
(r"\bAssignmentPlan\b", "reference to internal AssignmentPlan"),
|
|
27
|
+
(r"\bWorkcellSpec\b", "reference to internal WorkcellSpec"),
|
|
28
|
+
(r"\bDeviceSpec\b", "reference to internal DeviceSpec"),
|
|
29
|
+
(r"sk_[a-zA-Z0-9]{20,}", "possible API key literal"),
|
|
30
|
+
(r"/Users/\w+/", "absolute local path"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
SKIP_FILES = {"check_leakage.py", "conftest.py", "_leakage.py", "hatch_build.py"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def scan_file(path: Path, relative_to: Path | None = None) -> list[str]:
|
|
37
|
+
"""Scan a single Python file for forbidden patterns.
|
|
38
|
+
|
|
39
|
+
Returns a list of human-readable hit descriptions.
|
|
40
|
+
"""
|
|
41
|
+
if path.name in SKIP_FILES:
|
|
42
|
+
return []
|
|
43
|
+
text = path.read_text()
|
|
44
|
+
hits: list[str] = []
|
|
45
|
+
display = str(path.relative_to(relative_to)) if relative_to else str(path)
|
|
46
|
+
for lineno, line in enumerate(text.splitlines(), 1):
|
|
47
|
+
stripped = line.lstrip()
|
|
48
|
+
if stripped.startswith("#"):
|
|
49
|
+
continue
|
|
50
|
+
for pattern, reason in FORBIDDEN:
|
|
51
|
+
if re.search(pattern, line):
|
|
52
|
+
hits.append(f" {display}:{lineno}: {reason}\n {line.strip()}")
|
|
53
|
+
return hits
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def scan_source(src_dir: Path, relative_to: Path | None = None) -> list[str]:
|
|
57
|
+
"""Scan all Python files under a directory."""
|
|
58
|
+
hits: list[str] = []
|
|
59
|
+
for py in sorted(src_dir.rglob("*.py")):
|
|
60
|
+
hits.extend(scan_file(py, relative_to=relative_to))
|
|
61
|
+
return hits
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manual leakage check CLI.
|
|
3
|
+
|
|
4
|
+
The same check runs automatically during `hatch build` / `python -m build`
|
|
5
|
+
via the build hook in hatch_build.py. This script is for quick manual runs.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python check_leakage.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# Import from project root (not shipped inside the package)
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
18
|
+
|
|
19
|
+
from _leakage import scan_source # noqa: E402
|
|
20
|
+
|
|
21
|
+
ROOT = Path(__file__).parent
|
|
22
|
+
SRC = ROOT / "src" / "biocompute"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> int:
|
|
26
|
+
print("Scanning source files...")
|
|
27
|
+
hits = scan_source(SRC, relative_to=ROOT)
|
|
28
|
+
if hits:
|
|
29
|
+
print(f"\nFOUND {len(hits)} leakage(s):\n")
|
|
30
|
+
print("\n".join(hits))
|
|
31
|
+
print("\nFAILED: Fix leakage before publishing.")
|
|
32
|
+
return 1
|
|
33
|
+
print("PASSED: No leakage detected.")
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
sys.exit(main())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Hatchling build hook that runs leakage detection before building.
|
|
2
|
+
|
|
3
|
+
Automatically invoked by hatchling during `python -m build` or
|
|
4
|
+
`hatch build`. Aborts the build if any private internal references
|
|
5
|
+
are found in the source.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib.util
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
15
|
+
|
|
16
|
+
# Load _leakage.py directly to avoid importing the full biocompute
|
|
17
|
+
# package (which requires httpx, not available in the build env).
|
|
18
|
+
_leakage_path = Path(__file__).parent / "_leakage.py"
|
|
19
|
+
_spec = importlib.util.spec_from_file_location("_leakage", _leakage_path)
|
|
20
|
+
assert _spec is not None and _spec.loader is not None
|
|
21
|
+
_leakage = importlib.util.module_from_spec(_spec)
|
|
22
|
+
_spec.loader.exec_module(_leakage)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LeakageCheckHook(BuildHookInterface):
|
|
26
|
+
PLUGIN_NAME = "leakage-check"
|
|
27
|
+
|
|
28
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
29
|
+
src = Path(__file__).parent / "src" / "biocompute"
|
|
30
|
+
hits: list[str] = _leakage.scan_source(src, relative_to=Path(__file__).parent) # type: ignore[attr-defined]
|
|
31
|
+
|
|
32
|
+
if hits:
|
|
33
|
+
msg = f"Leakage check FAILED — {len(hits)} issue(s) found:\n" + "\n".join(hits)
|
|
34
|
+
raise RuntimeError(msg)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "biocompute"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "London Biocompute client library"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "London Biocompute" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"httpx>=0.27",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"mypy>=1.18",
|
|
26
|
+
"pytest>=9.0",
|
|
27
|
+
"pytest-httpx>=0.35",
|
|
28
|
+
"ruff>=0.14",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.hooks.custom]
|
|
32
|
+
|
|
33
|
+
[tool.mypy]
|
|
34
|
+
strict = true
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 120
|
|
38
|
+
target-version = "py312"
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["F", "B", "I"]
|
|
42
|
+
ignore = ["E402", "E501", "E701", "B007"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""biocompute - London Biocompute client library."""
|
|
2
|
+
|
|
3
|
+
from biocompute._version import __version__
|
|
4
|
+
from biocompute.competition import Competition, SubmissionResult, WellResult
|
|
5
|
+
from biocompute.exceptions import BiocomputeError
|
|
6
|
+
from biocompute.reagent import Reagent, blue_dye, green_dye, red_dye, water
|
|
7
|
+
from biocompute.well import Well, wells
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"__version__",
|
|
11
|
+
"Competition",
|
|
12
|
+
"SubmissionResult",
|
|
13
|
+
"WellResult",
|
|
14
|
+
"Well",
|
|
15
|
+
"wells",
|
|
16
|
+
"Reagent",
|
|
17
|
+
"red_dye",
|
|
18
|
+
"green_dye",
|
|
19
|
+
"blue_dye",
|
|
20
|
+
"water",
|
|
21
|
+
"BiocomputeError",
|
|
22
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|