diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..ae9cfdf --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,52 @@ +name: Documentation + +on: + push: + branches: + - main + +permissions: + contents: write + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-readthedocs: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e . + python -m pip install -r docs/requirements.txt + + - name: Update API reference record + run: python tools/update_api_reference.py + + - name: Build Read the Docs site + run: sphinx-build -b html -W --keep-going docs docs/_build/html + + - name: Commit API reference updates + run: | + if git diff --quiet -- docs/API_Reference.rst; then + echo "API reference is already current." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add docs/API_Reference.rst + git commit -m "docs: update API reference" + git push diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8a3e42f..5e15702 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,25 +1,16 @@ -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required version: 2 -# Set the OS, Python version, and other tools you might need build: os: ubuntu-24.04 tools: - python: "3.13" + python: "3.10" -# Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/conf.py + configuration: docs/conf.py + fail_on_warning: false -# Optionally, but recommended, -# declare the Python requirements required to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - method: pip path: . - - requirements: docs/requirements.txt - + - requirements: docs/requirements.txt diff --git a/README.md b/README.md index d409123..308e3e1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # BuildCompiler +[![Documentation Status](https://readthedocs.org/projects/buildcompiler/badge/?version=latest)](https://buildcompiler.readthedocs.io/en/latest/?badge=latest) + BuildCompiler is a Python compiler pipeline for synthetic biology build planning. It takes abstract SBOL designs and indexed biological inventory, then produces an executable build plan across domestication, MoClo assembly level 1, MoClo assembly level 2, transformation, and plating. This repository is being refactored around a clean architecture. The existing codebase is useful as working evidence, especially the level-1 assembly path and SBOL digestion/ligation behavior, but the new implementation should not preserve old APIs, old import paths, or legacy module boundaries except the root package name `buildcompiler`. @@ -12,6 +14,8 @@ Designing a genetic construct is easier than building it in the lab. BuildCompil abstract SBOL design + inventory -> build plan -> SBOL build artifacts -> PUDU JSON -> optional manual/OT-2 protocols ``` +The Read the Docs site is available at [buildcompiler.readthedocs.io](https://buildcompiler.readthedocs.io/en/latest/) and is built from the Sphinx documentation in `docs/`. + The compiler should answer: - Can this design be built from current inventory? diff --git a/docs/API_Reference.rst b/docs/API_Reference.rst index 8dd46bd..eb21fda 100644 --- a/docs/API_Reference.rst +++ b/docs/API_Reference.rst @@ -1,7 +1,411 @@ API Reference ============= -.. automodule:: sbol2build.sbol2build +.. This file is generated by tools/update_api_reference.py. +.. Run `python tools/update_api_reference.py` after adding or removing public modules. + +Public API +---------- + +buildcompiler.api.compiler +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.api.compiler + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.api.options +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.api.options + :members: + :undoc-members: + :show-inheritance: + +Stages +------ + +buildcompiler.stages.assembly_lvl1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.stages.assembly_lvl1 + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.stages.assembly_lvl2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.stages.assembly_lvl2 + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.stages.domestication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.stages.domestication + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.stages.transformation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.stages.transformation + :members: + :undoc-members: + :show-inheritance: + +Planning +-------- + +buildcompiler.planning.classifier +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.classifier + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.planning.combinatorial +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.combinatorial + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.planning.domestication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.domestication + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.planning.full_build_planner +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.full_build_planner + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.planning.models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.models + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.planning.validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.planning.validation + :members: + :undoc-members: + :show-inheritance: + +Inventory +--------- + +buildcompiler.inventory.compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.inventory.compatibility + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.inventory.inventory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.inventory.inventory + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.inventory.selector +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.inventory.selector + :members: + :undoc-members: + :show-inheritance: + +SBOL helpers +------------ + +buildcompiler.sbol.assembly +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.sbol.assembly + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.sbol.domestication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.sbol.domestication + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.sbol.resolver +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.sbol.resolver + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.sbol.transformation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.sbol.transformation + :members: + :undoc-members: + :show-inheritance: + +Domain models +------------- + +buildcompiler.domain.approvals +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.approvals + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.build_request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.build_request + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.build_result +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.build_result + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.build_stage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.build_stage + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.design +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.design + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.material_state +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.material_state + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.missing_input +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.missing_input + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.plasmid +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.plasmid + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.reagent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.reagent + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.status +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.status + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.domain.warnings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.domain.warnings + :members: + :undoc-members: + :show-inheritance: + +Execution +--------- + +buildcompiler.execution.context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.execution.context + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.execution.executor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.execution.executor + :members: + :undoc-members: + :show-inheritance: + +Adapters +-------- + +buildcompiler.adapters.opentrons.simulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.adapters.opentrons.simulation + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.adapters.protocols +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.adapters.protocols + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.adapters.pudu.assembly_json +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.adapters.pudu.assembly_json + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.adapters.pudu.plating_json +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.adapters.pudu.plating_json + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.adapters.pudu.transformation_json +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.adapters.pudu.transformation_json + :members: + :undoc-members: + :show-inheritance: + +Reporting +--------- + +buildcompiler.reporting.graph +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.reporting.graph + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.reporting.report +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.reporting.report + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.reporting.summary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.reporting.summary + :members: + :undoc-members: + :show-inheritance: + +Legacy modules +-------------- + +buildcompiler.abstract_translator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.abstract_translator + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.buildcompiler +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.buildcompiler + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.constants +~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.constants + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.plasmid +~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.plasmid + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.robotutils +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.robotutils + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.sbol2build +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.sbol2build + :members: + :undoc-members: + :show-inheritance: + +buildcompiler.transformation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: buildcompiler.transformation :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/docs/Tutorials.rst b/docs/Tutorials.rst index 98d58a7..09c60fa 100644 --- a/docs/Tutorials.rst +++ b/docs/Tutorials.rst @@ -1,88 +1,10 @@ Tutorials -====================================== -Let's get started with creating DNA assembly plans. First we will demonstrate a workflow using plasmid files from `SBOLCanvas `_: +========= -.. code:: ipython3 +The tutorials have been reorganized into focused pages: - import sbol2build as s2b - import sbol2 - -First, read your plasmid SBOL files into documents. - -.. code:: ipython3 - - promoter = sbol2.Document() - promoter.read('pro_in_bb.xml') - - rbs = sbol2.Document() - rbs.read('rbs_in_bb.xml') - - cds = sbol2.Document() - cds.read('cds_in_bb.xml') - - terminator = sbol2.Document() - terminator.read('terminator_in_bb.xml') - - backbone = sbol2.Document() - backbone.read('backbone.xml') - -Create golden gate assembly plan object with all the parts, the acceptor backbone, and restriction enzyme. - -.. code:: ipython3 - - assembly_doc = sbol2.Document() - - assembly_plan = s2b.golden_gate_assembly_plan('tutorial_assembly_plan', [promoter, rbs, cds, terminator], backbone, 'BsaI', assembly_doc) - - composites = assembly_plan.run() - -Full build level-2 PUDU artifact package ----------------------------------------- - -The legacy artifact-producing compiler can run a level-2 design and package all -generated SBOL, JSON, and PUDU protocol inputs into a single zip file. If the -level-2 design is missing level-1 region inputs, ``full_build`` attempts level-1 -assembly, falls back to domestication for missing parts, then retries the -downstream assemblies. - -.. code:: python - - from pathlib import Path - - import sbol2 - - from buildcompiler.buildcompiler import BuildCompiler - - test_files = Path("tests/test_files") - collection_docs = [] - for filename in ( - "CIDARMoCloParts_collection.xml", - "CIDARMoCloPlasmidsKit_collection.xml", - "Enzyme_Implementations_collection.xml", - "impl_test_collection.xml", - ): - doc = sbol2.Document() - doc.read(str(test_files / filename)) - collection_docs.append(doc) - - lvl2_design_doc = sbol2.Document() - lvl2_design_doc.read(str(test_files / "ExampleLvl2_design.xml")) - - compiler = BuildCompiler.from_local_documents( - collection_docs, - design_doc=lvl2_design_doc, - ) - result = compiler.full_build( - designs=lvl2_design_doc, - results_dir="results/full_build_lvl2_pudu", - overwrite=True, - ) - - print(result["zip_path"]) - -The zip archive includes ``full_build_manifest.json`` plus PUDU assembly, -transformation, and plating inputs/scripts such as -``assembly_lvl2_pudu_assembly_input.json``, -``assembly_lvl1_pudu_assembly_input.json``, -``domestication_pudu_assembly_input.json``, -``pudu_transformation_protocol.py``, and ``pudu_plating_protocol.py``. +* :doc:`quickstart` +* :doc:`examples/offline_lvl1` +* :doc:`examples/transformation_pudu` +* :doc:`examples/full_build` +* :doc:`notebooks` diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/_static/.gitkeep @@ -0,0 +1 @@ + diff --git a/docs/conf.py b/docs/conf.py index ea066a8..0a5ef7e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,39 +1,45 @@ -import os -import sys -sys.path.insert(0, os.path.abspath('../src')) #adjust file path - -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html +"""Sphinx configuration for BuildCompiler documentation.""" -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +from __future__ import annotations -project = 'SBOL2Build' -copyright = '2025, Ryan Greer' -author = 'Ryan Greer' -release = '0.0b1' +import os +import sys +from importlib import metadata -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +sys.path.insert(0, os.path.abspath("../src")) -extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode"] +project = "BuildCompiler" +copyright = "2026, BuildCompiler contributors" +author = "BuildCompiler contributors" -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +try: + release = metadata.version("synbio-buildcompiler") +except metadata.PackageNotFoundError: + release = "0.0.0" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +] +autosummary_generate = True +autodoc_typehints = "description" +autodoc_member_order = "bysource" -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -html_theme = 'furo' -html_static_path = ['_static'] +html_theme = "furo" +html_title = "BuildCompiler" +html_static_path = ["_static"] +html_logo = "../images/buildcompiler_logo.png" html_theme_options = { + "source_repository": "https://github.com/MyersResearchGroup/BuildCompiler/", + "source_branch": "main", + "source_directory": "docs/", "light_css_variables": {}, "dark_css_variables": {}, - "default_color_mode": "light", - "color_mode_toggle": True, -} \ No newline at end of file +} diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..91c5211 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,48 @@ +Development +=========== + +Local checks +------------ + +Install the package and test dependencies: + +.. code-block:: bash + + python -m pip install -e ".[test]" + +Run the focused tests for the documented PUDU path: + +.. code-block:: bash + + python -m pytest \ + tests/unit/adapters/pudu/test_transformation_json.py \ + tests/unit/adapters/pudu/test_plating_json.py \ + tests/test_buildcompiler_transformation.py + +Build docs locally: + +.. code-block:: bash + + python -m pip install -r docs/requirements.txt + sphinx-build -b html docs docs/_build/html + +Agent handoff +------------- + +Repository-specific agent instructions are in ``AGENTS.md``. Recent handoff +notes for the PUDU integration and notebook artifacts are in +``AGENTS_log.md``. + +Read the Docs deployment +------------------------ + +The repository is configured for Read the Docs with ``.readthedocs.yaml``. +To publish the hosted site: + +1. Push this branch to GitHub. +2. Import the GitHub repository in Read the Docs. +3. Select the branch containing ``.readthedocs.yaml``. +4. Trigger a build. + +Read the Docs will install the package and docs dependencies, then run Sphinx +against ``docs/conf.py``. diff --git a/docs/examples/full_build.rst b/docs/examples/full_build.rst new file mode 100644 index 0000000..5be0bed --- /dev/null +++ b/docs/examples/full_build.rst @@ -0,0 +1,80 @@ +Full Build Workflows +==================== + +Use case +-------- + +You want BuildCompiler to resolve a higher-level design, trigger upstream +stages when material is missing, and package all generated build artifacts. + +The legacy artifact-producing ``BuildCompiler.full_build`` path can produce +SBOL, JSON, PUDU inputs, PUDU protocol scripts, and a manifest. + +Level-2 build example +--------------------- + +.. code-block:: python + + from pathlib import Path + + import sbol2 + + from buildcompiler.buildcompiler import BuildCompiler + + test_files = Path("tests/test_files") + collection_docs = [] + for filename in ( + "CIDARMoCloParts_collection.xml", + "CIDARMoCloPlasmidsKit_collection.xml", + "Enzyme_Implementations_collection.xml", + "impl_test_collection.xml", + ): + doc = sbol2.Document() + doc.read(str(test_files / filename)) + collection_docs.append(doc) + + lvl2_design_doc = sbol2.Document() + lvl2_design_doc.read(str(test_files / "ExampleLvl2_design.xml")) + + compiler = BuildCompiler.from_local_documents( + collection_docs, + design_doc=lvl2_design_doc, + ) + result = compiler.full_build( + designs=lvl2_design_doc, + results_dir="results/full_build_lvl2", + overwrite=True, + ) + + print(result["zip_path"]) + +Expected artifacts +------------------ + +Depending on which inputs are already available, full build may emit: + +* ``domestication_pudu_assembly_input.json`` +* ``assembly_lvl1_pudu_assembly_input.json`` +* ``assembly_lvl2_pudu_assembly_input.json`` +* ``transformation_pudu_input.json`` +* ``plating_pudu_input.json`` +* ``pudu_transformation_protocol.py`` +* ``pudu_plating_protocol.py`` +* ``full_build_manifest.json`` + +Representative generated examples are stored under: + +.. code-block:: text + + notebooks/results/full_build_workflow_examples/ + +Behavior +-------- + +The full-build planner/executor is intended to: + +* use existing inventory when possible, +* trigger level-1 assembly when a level-2 design is missing a region plasmid, +* trigger domestication when level-1 assembly is missing a part plasmid, +* retry downstream stages after generated products are indexed, and +* report partial success or missing inputs instead of silently failing. diff --git a/docs/examples/offline_lvl1.rst b/docs/examples/offline_lvl1.rst new file mode 100644 index 0000000..9c158f7 --- /dev/null +++ b/docs/examples/offline_lvl1.rst @@ -0,0 +1,91 @@ +Offline Level-1 Assembly +======================== + +Use case +-------- + +You have an abstract level-1 SBOL design and local SBOL collections containing +the required part plasmids, backbone plasmids, and enzyme implementations. You +want BuildCompiler to produce: + +* an assembled product SBOL document, +* a deterministic PUDU assembly input JSON file, and +* a list of route inputs used to build the plasmid. + +Example +------- + +.. code-block:: python + + from pathlib import Path + + import sbol2 + + from buildcompiler.abstract_translator import extract_toplevel_definition + from buildcompiler.adapters.pudu import write_assembly_pudu_input_json + from buildcompiler.buildcompiler import BuildCompiler + + test_files = Path("tests/test_files") + collection_docs = [] + for filename in ( + "CIDARMoCloParts_collection.xml", + "CIDARMoCloPlasmidsKit_collection.xml", + "Enzyme_Implementations_collection.xml", + "impl_test_collection.xml", + ): + doc = sbol2.Document() + doc.read(str(test_files / filename)) + collection_docs.append(doc) + + design_doc = sbol2.Document() + design_doc.read(str(test_files / "abstract_design.xml")) + design = extract_toplevel_definition(design_doc) + + compiler = BuildCompiler.from_local_documents( + collection_docs, + design_doc=design_doc, + ) + + product_doc = sbol2.Document() + routes, product_doc = compiler.assembly_lvl1( + [design], + final_doc=product_doc, + product_name="offline_lvl1", + ) + + results = Path("results/offline_lvl1") + results.mkdir(parents=True, exist_ok=True) + (results / "offline_lvl1_products.xml").write_text( + product_doc.writeString(), + encoding="utf-8", + ) + write_assembly_pudu_input_json( + compiler.last_assembly_pudu_json, + results / "offline_lvl1_pudu_input.json", + ) + +The generated PUDU input has this shape: + +.. code-block:: json + + [ + { + "Product": "http://buildcompiler.org/offline_lvl1/1", + "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1", + "PartsList": [ + "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1", + "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1", + "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1", + "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1" + ], + "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1" + } + ] + +Notes +----- + +``compiler.last_assembly_pudu_json`` is populated by the assembly stage from +the route selected by BuildCompiler. It does not scrape the generated SBOL +document, so it retains the product plasmid, backbone plasmid, part plasmids, +and enzyme implementation used by the route. diff --git a/docs/examples/transformation_pudu.rst b/docs/examples/transformation_pudu.rst new file mode 100644 index 0000000..f1f9de9 --- /dev/null +++ b/docs/examples/transformation_pudu.rst @@ -0,0 +1,146 @@ +Transformation and PUDU Protocol Chain +====================================== + +Use case +-------- + +You have run level-1 assembly and want to: + +* transform the assembled product into a chassis strain, +* generate PUDU-compatible transformation JSON, +* generate PUDU assembly, transformation, and plating protocols, and +* simulate the full OT-2 handoff chain. + +Notebook +-------- + +The full executable example lives in: + +.. code-block:: text + + notebooks/buildcompiler_transformation_quickstart.ipynb + +It produces artifacts in: + +.. code-block:: text + + notebooks/results/buildcompiler_transformation_quickstart/ + +BuildCompiler transformation +---------------------------- + +After running ``assembly_lvl1``, pass the structured assembly products directly +to ``BuildCompiler.transformation``: + +.. code-block:: python + + transformation_result = compiler.transformation( + assembly_products, + chassis_name="E_coli_DH5alpha", + transformation_doc=assembly_doc, + ) + + transformation_spec = transformations_to_pudu_json( + strain_identities=[ + artifact["transformed_strain_module"] + for artifact in transformation_result["sbol_artifacts"] + ], + chassis_identities=["https://sbolcanvas.org/DH5alpha/1"], + plasmid_sets=[ + [product.plasmid_definition.identity] + for product in assembly_products + ], + ) + +PUDU transformation spec: + +.. code-block:: json + + [ + { + "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_transformation_lvl1/1", + "Chassis": "https://sbolcanvas.org/DH5alpha/1", + "Plasmids": [ + "http://buildcompiler.org/standard_GFP_transformation_lvl1/1" + ] + } + ] + +PUDU protocol generation +------------------------ + +PUDU's documented handoff is: + +.. code-block:: text + + assembly_input.json + -> opentrons_simulate assembly_protocol.py + -> transformation_input.json + -> opentrons_simulate transformation_protocol.py + -> plating_input.json + -> opentrons_simulate plating_protocol.py + -> plating_layout.json / plating_layout.xlsx + +The notebook uses PUDU's Python API: + +.. code-block:: python + + from pudu.generate_protocol import detect_protocol_type, generate_protocol + + assembly_protocol = generate_protocol( + protocol_data=assembly_pudu, + protocol_type="assembly", + assembly_subtype="SBOL", + ) + + transformation_protocol = generate_protocol( + protocol_data=transformation_spec, + protocol_type="transformation", + plasmid_locations=plasmid_locations, + ) + + plating_protocol = generate_protocol( + protocol_data=plating_input, + protocol_type="plating", + ) + +The CLI equivalent is: + +.. code-block:: bash + + python -m pudu.generate_protocol assembly_input.json -o assembly_protocol.py --protocol-type assembly + opentrons_simulate assembly_protocol.py + + python -m pudu.generate_protocol transformation_spec.json -o transformation_protocol.py --protocol-type transformation --plasmid-locations transformation_input.json + opentrons_simulate transformation_protocol.py + + python -m pudu.generate_protocol plating_input.json -o plating_protocol.py --protocol-type plating + opentrons_simulate plating_protocol.py + +Generated files +--------------- + +The validated notebook run generated: + +* ``pudu_assembly_protocol.py`` +* ``transformation_input.json`` +* ``pudu_transformation_protocol.py`` +* ``plating_input.json`` +* ``pudu_plating_protocol.py`` +* ``plating_layout.json`` +* ``plating_layout.xlsx`` + +The final plating input shape matches PUDU: + +.. code-block:: json + + { + "bacterium_locations": { + "A1": [ + "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1", + "Competent_Cell_DH5alpha", + "standard_GFP_transformation_lvl1", + "Media_1" + ] + } + } diff --git a/docs/index.rst b/docs/index.rst index 5669efb..9060e2f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,32 +1,61 @@ -.. SBOL2Build documentation master file, created by - sphinx-quickstart on Mon Apr 14 14:04:32 2025. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +BuildCompiler +============= -Welcome to SBOL2Build's documentation! -====================================== +BuildCompiler compiles SBOL designs into practical synthetic biology build +workflows. It indexes available parts, plasmids, backbones, and reagents, then +plans the build steps needed to produce constructs through domestication, +MoClo assembly, transformation, and plating. -SBOL2Build is a Python library for assisting in the planning, documentation, and distribution of DNA assembly plans using the Synthetic Biology Open Language 2.3 data standard. +The repository currently exposes two useful layers: -It was developed to support build functionality and workflows in `SynBioSuite `_, based off the `SBOL Best Practices `_. +* A legacy, artifact-producing compiler in :mod:`buildcompiler.buildcompiler` + that is used by the offline notebooks and PUDU examples. +* A newer modular API in :mod:`buildcompiler.api`, :mod:`buildcompiler.stages`, + :mod:`buildcompiler.planning`, and :mod:`buildcompiler.execution`. +The examples in this documentation focus on offline, reproducible workflows +using the SBOL fixture collections in ``tests/test_files``. + +Core workflow +------------- + +.. code-block:: text + + SBOL design + local SBOL collections + -> BuildCompiler inventory/indexing + -> domestication / assembly level 1 / assembly level 2 + -> transformation + -> plating + -> SBOL artifacts + PUDU JSON + optional OT-2 protocols + +Representative use cases +------------------------ + +* Run a level-1 MoClo assembly from a local abstract design. +* Generate PUDU-compatible assembly JSON for OT-2 protocol generation. +* Transform an assembled plasmid into a chassis strain. +* Simulate the PUDU assembly, transformation, and plating protocol chain. +* Run a full build that can trigger upstream stages when inputs are missing. .. toctree:: :maxdepth: 2 - :caption: Contents: - - Tutorials - API_Reference + :caption: User Guide -============ -Installation -============ + installation + quickstart + examples/offline_lvl1 + examples/transformation_pudu + examples/full_build + notebooks -Install SBOL2Build with: - -.. code:: python - - pip install sbol2build +.. toctree:: + :hidden: + Tutorials +.. toctree:: + :maxdepth: 2 + :caption: Reference + API_Reference + development diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..bb4c440 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,54 @@ +Installation +============ + +Python version +-------------- + +BuildCompiler currently targets Python 3.10 and newer. + +Editable install +---------------- + +For development or notebook use from a checkout: + +.. code-block:: bash + + python -m pip install -e . + +Install test dependencies: + +.. code-block:: bash + + python -m pip install -e ".[test]" + +Optional automation dependencies +-------------------------------- + +PUDU and Opentrons support are optional. BuildCompiler can emit PUDU-compatible +JSON without importing PUDU. To generate or simulate OT-2 protocols, install the +automation dependencies or use a local PUDU checkout: + +.. code-block:: bash + + python -m pip install -e ".[automation,test]" + +In the development environment used for the repository examples, PUDU was +available as a sibling checkout: + +.. code-block:: text + + /Users/gonzalovidal/Documents/GitHub/PUDU + +Read the Docs +------------- + +This repository includes ``.readthedocs.yaml``. Read the Docs installs the +package, installs ``docs/requirements.txt``, and builds ``docs/conf.py``. + +To build the docs locally: + +.. code-block:: bash + + python -m pip install -e . + python -m pip install -r docs/requirements.txt + sphinx-build -b html docs docs/_build/html diff --git a/docs/notebooks.rst b/docs/notebooks.rst new file mode 100644 index 0000000..74a77e4 --- /dev/null +++ b/docs/notebooks.rst @@ -0,0 +1,32 @@ +Notebooks +========= + +Notebook examples are kept in the repository so users can inspect and execute +real SBOL workflows. + +Recommended starting points +--------------------------- + +``notebooks/buildcompiler_offline_quickstart.ipynb`` + Offline SBOL collection loading, level-1 assembly, level-2 assembly, + domestication, and full-build examples. + +``notebooks/buildcompiler_transformation_quickstart.ipynb`` + Offline level-1 assembly followed by BuildCompiler transformation and the + complete PUDU assembly/transformation/plating simulation chain. + +``notebooks/full_build_workflow_examples.ipynb`` + Full-build artifact packaging examples. + +Notebook result directories +--------------------------- + +Generated outputs are stored under ``notebooks/results``. The most useful +directory for automation integration is: + +.. code-block:: text + + notebooks/results/buildcompiler_transformation_quickstart/ + +It contains generated PUDU protocol scripts, simulator logs, intermediate JSON +handoff files, and the final plating layout. diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..81782e3 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,84 @@ +Quickstart +========== + +This quickstart uses only files that ship with the repository. It does not +require SynBioHub network access. + +Install the package in editable mode: + +.. code-block:: bash + + python -m pip install -e ".[test]" + +Load the local fixture collections: + +.. code-block:: python + + from pathlib import Path + + import sbol2 + + from buildcompiler.abstract_translator import extract_toplevel_definition + from buildcompiler.buildcompiler import BuildCompiler + + repo = Path.cwd() + test_files = repo / "tests" / "test_files" + + collection_paths = [ + test_files / "CIDARMoCloParts_collection.xml", + test_files / "CIDARMoCloPlasmidsKit_collection.xml", + test_files / "Enzyme_Implementations_collection.xml", + test_files / "impl_test_collection.xml", + ] + + collection_docs = [] + for path in collection_paths: + doc = sbol2.Document() + doc.read(str(path)) + collection_docs.append(doc) + + design_doc = sbol2.Document() + design_doc.read(str(test_files / "abstract_design.xml")) + design = extract_toplevel_definition(design_doc) + + compiler = BuildCompiler.from_local_documents( + collection_docs, + design_doc=design_doc, + ) + +Run level-1 assembly: + +.. code-block:: python + + product_doc = sbol2.Document() + assembly_routes, product_doc = compiler.assembly_lvl1( + [design], + final_doc=product_doc, + product_name="quickstart_lvl1", + ) + + products = assembly_routes[design.identity] + print([product.plasmid_definition.displayId for product in products]) + +Write SBOL and PUDU JSON artifacts: + +.. code-block:: python + + import json + + from buildcompiler.adapters.pudu import write_assembly_pudu_input_json + + results = repo / "docs_results" / "quickstart" + results.mkdir(parents=True, exist_ok=True) + + (results / "quickstart_lvl1_products.xml").write_text( + product_doc.writeString(), + encoding="utf-8", + ) + write_assembly_pudu_input_json( + compiler.last_assembly_pudu_json, + results / "quickstart_lvl1_pudu_input.json", + ) + +``Document.writeString()`` is used in offline examples because +``sbol2.Document.write()`` may attempt online validation. diff --git a/docs/requirements.txt b/docs/requirements.txt index 27fedc4..9103101 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx -furo \ No newline at end of file +sphinx>=7 +furo>=2024.1.29 diff --git a/tools/update_api_reference.py b/tools/update_api_reference.py new file mode 100644 index 0000000..5cd8053 --- /dev/null +++ b/tools/update_api_reference.py @@ -0,0 +1,103 @@ +"""Generate the Sphinx API reference page for BuildCompiler.""" + +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +PACKAGE_ROOT = REPO_ROOT / "src" / "buildcompiler" +OUTPUT_PATH = REPO_ROOT / "docs" / "API_Reference.rst" + +SECTION_ORDER = [ + ("Public API", "buildcompiler.api"), + ("Stages", "buildcompiler.stages"), + ("Planning", "buildcompiler.planning"), + ("Inventory", "buildcompiler.inventory"), + ("SBOL helpers", "buildcompiler.sbol"), + ("Domain models", "buildcompiler.domain"), + ("Execution", "buildcompiler.execution"), + ("Adapters", "buildcompiler.adapters"), + ("Reporting", "buildcompiler.reporting"), + ("Legacy modules", "buildcompiler"), +] + +LEGACY_MODULES = { + "buildcompiler.abstract_translator", + "buildcompiler.buildcompiler", + "buildcompiler.constants", + "buildcompiler.plasmid", + "buildcompiler.robotutils", + "buildcompiler.sbol2build", + "buildcompiler.transformation", +} + +EXCLUDED_MODULES = {"buildcompiler.__init__"} + + +def underline(text: str, char: str = "=") -> str: + return char * len(text) + + +def module_name(path: Path) -> str: + relative = path.relative_to(REPO_ROOT / "src").with_suffix("") + return ".".join(relative.parts) + + +def discover_modules() -> list[str]: + modules = [] + for path in sorted(PACKAGE_ROOT.rglob("*.py")): + name = module_name(path) + if name.endswith(".__init__") or name in EXCLUDED_MODULES: + continue + modules.append(name) + return modules + + +def modules_for_section(modules: list[str], package: str) -> list[str]: + if package == "buildcompiler": + return [name for name in modules if name in LEGACY_MODULES] + prefix = f"{package}." + return [name for name in modules if name.startswith(prefix)] + + +def render_module(name: str) -> list[str]: + return [ + name, + underline(name, "~"), + "", + f".. automodule:: {name}", + " :members:", + " :undoc-members:", + " :show-inheritance:", + "", + ] + + +def render_api_reference() -> str: + modules = discover_modules() + lines = [ + "API Reference", + "=============", + "", + ".. This file is generated by tools/update_api_reference.py.", + ".. Run `python tools/update_api_reference.py` after adding or removing public modules.", + "", + ] + + for title, package in SECTION_ORDER: + section_modules = modules_for_section(modules, package) + if not section_modules: + continue + lines.extend([title, underline(title, "-"), ""]) + for name in section_modules: + lines.extend(render_module(name)) + + return "\n".join(lines).rstrip() + "\n" + + +def main() -> None: + OUTPUT_PATH.write_text(render_api_reference(), encoding="utf-8") + + +if __name__ == "__main__": + main()