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.
@@ -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"