Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/buildcompiler/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
"""Package scaffolding for clean architecture."""
"""Public API contracts and options for BuildCompiler."""

from .options import (
ApprovalOptions,
BuildOptions,
CombinatorialOptions,
DomesticationOptions,
ExecutionOptions,
Lvl2SearchOptions,
PlanningOptions,
ProtocolMode,
ProtocolOptions,
ReagentOptions,
ReportingOptions,
SelectionOptions,
)

__all__ = [
"ApprovalOptions",
"BuildOptions",
"CombinatorialOptions",
"DomesticationOptions",
"ExecutionOptions",
"Lvl2SearchOptions",
"PlanningOptions",
"ProtocolMode",
"ProtocolOptions",
"ReagentOptions",
"ReportingOptions",
"SelectionOptions",
]
89 changes: 89 additions & 0 deletions src/buildcompiler/api/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Build options contracts for full_build configuration."""

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Literal


class ProtocolMode(str, Enum):
"""Protocol generation mode."""

NONE = "none"
MANUAL = "manual"
AUTOMATED = "automated"


@dataclass
class CombinatorialOptions:
max_variants: int = 256
allow_large_expansion: bool = False


@dataclass
class Lvl2SearchOptions:
max_exhaustive_region_count: int = 4
allow_large_order_search: bool = False


@dataclass
class PlanningOptions:
combinatorial: CombinatorialOptions = field(default_factory=CombinatorialOptions)
lvl2_search: Lvl2SearchOptions = field(default_factory=Lvl2SearchOptions)


@dataclass
class ExecutionOptions:
max_iterations: int = 5
continue_on_error: bool = False


@dataclass
class SelectionOptions:
prefer_existing_collection_material: bool = True
prefer_higher_material_state: bool = True


@dataclass
class ProtocolOptions:
mode: ProtocolMode = ProtocolMode.NONE
simulate: bool = False
results_dir: str | Path | None = None


@dataclass
class ReportingOptions:
include_detailed_report: bool = False
include_rejected_routes: bool = True
max_rejected_routes: int = 3


@dataclass
class ApprovalOptions:
approved_processes: set[str] = field(default_factory=set)
approved_approval_ids: set[str] = field(default_factory=set)
scope: Literal["run", "persistent"] = "run"


@dataclass
class ReagentOptions:
allow_reagent_purchase: bool = False
default_restriction_enzyme: str = "BsaI"
default_ligase: str = "T4_DNA_ligase"


@dataclass
class DomesticationOptions:
allow_sequence_domestication_edits: bool = False


@dataclass
class BuildOptions:
planning: PlanningOptions = field(default_factory=PlanningOptions)
execution: ExecutionOptions = field(default_factory=ExecutionOptions)
selection: SelectionOptions = field(default_factory=SelectionOptions)
protocol: ProtocolOptions = field(default_factory=ProtocolOptions)
reporting: ReportingOptions = field(default_factory=ReportingOptions)
approvals: ApprovalOptions = field(default_factory=ApprovalOptions)
reagents: ReagentOptions = field(default_factory=ReagentOptions)
domestication: DomesticationOptions = field(default_factory=DomesticationOptions)
37 changes: 37 additions & 0 deletions tests/unit/api/test_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from buildcompiler.api import BuildOptions, ProtocolMode


def test_build_options_defaults_match_contract():
options = BuildOptions()

assert options.execution.max_iterations == 5
assert options.execution.continue_on_error is False
assert options.protocol.mode == ProtocolMode.NONE
assert options.protocol.simulate is False
assert options.reagents.allow_reagent_purchase is False
assert options.reagents.default_restriction_enzyme == "BsaI"
assert options.reagents.default_ligase == "T4_DNA_ligase"
assert options.domestication.allow_sequence_domestication_edits is False
assert options.planning.combinatorial.max_variants == 256
assert options.planning.combinatorial.allow_large_expansion is False
assert options.planning.lvl2_search.max_exhaustive_region_count == 4
assert options.planning.lvl2_search.allow_large_order_search is False
assert options.reporting.include_rejected_routes is True
assert options.reporting.max_rejected_routes == 3
assert options.approvals.approved_processes == set()
assert options.approvals.approved_approval_ids == set()


def test_mutable_defaults_are_isolated_across_instances():
left = BuildOptions()
right = BuildOptions()

left.approvals.approved_processes.add("biosafety")
left.approvals.approved_approval_ids.add("approval-1")
left.planning.combinatorial.max_variants = 1024
left.reporting.max_rejected_routes = 10

assert right.approvals.approved_processes == set()
assert right.approvals.approved_approval_ids == set()
assert right.planning.combinatorial.max_variants == 256
assert right.reporting.max_rejected_routes == 3
Loading