From d0a041bb2a79f9533125d641ec9ce60779892f92 Mon Sep 17 00:00:00 2001 From: Ryan Greer Date: Fri, 14 Nov 2025 09:16:57 -0700 Subject: [PATCH 1/4] abstract_design_2_plasmids utility function --- sbs_server/app/utils.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 sbs_server/app/utils.py diff --git a/sbs_server/app/utils.py b/sbs_server/app/utils.py new file mode 100644 index 0000000..f893f3c --- /dev/null +++ b/sbs_server/app/utils.py @@ -0,0 +1,39 @@ +import sbol2 +import sbol2build as s2b +from typing import List + +def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: str, plasmid_vector_uri: str, sbh: sbol2.PartShop) -> List[sbol2.Document]: + abstract_design_doc = sbol2.Document() + sbh.pull( + abstract_design_uri, + abstract_design_doc + ) + + plasmid_collection_doc = sbol2.Document() + sbh.pull( + plasmid_collection_uri, + plasmid_collection_doc + ) + + backbone_doc = sbol2.Document() + sbh.pull( + plasmid_vector_uri, + backbone_doc, + ) + + mocloplasmid_list = s2b.abstract_translator.translate_abstract_to_plasmids( + abstract_design_doc, plasmid_collection_doc, backbone_doc + ) + + part_documents = [] + for mocloPlasmid in mocloplasmid_list: + temp_doc = sbol2.Document() + mocloPlasmid.definition.copy(temp_doc) + s2b.abstract_translator.copy_sequences( + mocloPlasmid.definition, + temp_doc, + plasmid_collection_doc + ) + part_documents.append(temp_doc) + + return part_documents From 5be2c8e6ff70c653317fb6079c64b21f88605e57 Mon Sep 17 00:00:00 2001 From: Ryan Greer Date: Fri, 14 Nov 2025 14:54:39 -0700 Subject: [PATCH 2/4] added sbol2build_moclo helper for running assembly plan --- sbs_server/app/views.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sbs_server/app/views.py b/sbs_server/app/views.py index f83ccee..db5e2b8 100644 --- a/sbs_server/app/views.py +++ b/sbs_server/app/views.py @@ -130,7 +130,26 @@ def upload_file_from_sbs_post_up(): "status": "success" } return jsonify(sbs_upload_response_dict) + +@app.route('/abstract_design_2_plasmids', methods=['POST']) +def abstract_design_2_plasmids(): + if 'abstract_design_uri' not in request.form: + return jsonify({"error": "Missing abstract design URI"}), 400 + if 'plasmid_collection_uri' not in request.form: + return jsonify({"error": "Missing plasmid collection URI"}), 400 + if 'plasmid_vector_uri' not in request.form: + return jsonify({"error": "Missing plasmid vector URI"}), 400 + + #TODO authtoken and registry errorhandling? + abstract_design_uri = request.form.get("abstract_design_uri") + plasmid_collection_uri = request.form.get("plasmid_collection_uri") + plasmid_vector_uri = request.form.get("plasmid_vector_uri") + auth_token = request.form.get("auth_token") + + print(abstract_design_uri, plasmid_collection_uri, plasmid_vector_uri, auth_token) + + return 0 @app.route('/sbol_2_build_golden_gate', methods=['POST']) From 758c06372ba3d7a6e9a76242afc65074bd7d1fda Mon Sep 17 00:00:00 2001 From: Ryan Greer Date: Fri, 14 Nov 2025 15:03:33 -0700 Subject: [PATCH 3/4] displayId --- sbs_server/app/utils.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/sbs_server/app/utils.py b/sbs_server/app/utils.py index f893f3c..6a210df 100644 --- a/sbs_server/app/utils.py +++ b/sbs_server/app/utils.py @@ -1,13 +1,15 @@ import sbol2 import sbol2build as s2b -from typing import List +from typing import List, Tuple -def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: str, plasmid_vector_uri: str, sbh: sbol2.PartShop) -> List[sbol2.Document]: +def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: str, plasmid_vector_uri: str, sbh: sbol2.PartShop) -> Tuple[List[sbol2.Document], sbol2.Document, str]: abstract_design_doc = sbol2.Document() sbh.pull( abstract_design_uri, abstract_design_doc ) + + abstract_design_id = s2b.abstract_translator.extract_toplevel_definition(abstract_design_doc).displayId plasmid_collection_doc = sbol2.Document() sbh.pull( @@ -36,4 +38,18 @@ def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: ) part_documents.append(temp_doc) - return part_documents + return part_documents, backbone_doc, abstract_design_id + +def sbol2build_moclo(part_documents: List[sbol2.Document], backbone_doc: sbol2.document, abstract_design_id: str) -> sbol2.Document: + assembly_doc = sbol2.Document() + assembly_obj = s2b.golden_gate_assembly_plan( + f"{abstract_design_id}_assembly_plan", + part_documents, + backbone_doc, + "BsaI", + assembly_doc + ) + + composite_list = assembly_obj.run() + + return assembly_doc \ No newline at end of file From 46c359af73d4712a810b833479ccc576232b5b16 Mon Sep 17 00:00:00 2001 From: Ryan Greer Date: Mon, 17 Nov 2025 17:29:37 -0700 Subject: [PATCH 4/4] upload_assembly endpoint --- sbs_server/app/utils.py | 10 +++-- sbs_server/app/views.py | 90 +++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 62 deletions(-) diff --git a/sbs_server/app/utils.py b/sbs_server/app/utils.py index 6a210df..54a1556 100644 --- a/sbs_server/app/utils.py +++ b/sbs_server/app/utils.py @@ -1,5 +1,7 @@ import sbol2 import sbol2build as s2b +from sbol2build import abstract_translator as at + from typing import List, Tuple def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: str, plasmid_vector_uri: str, sbh: sbol2.PartShop) -> Tuple[List[sbol2.Document], sbol2.Document, str]: @@ -9,7 +11,7 @@ def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: abstract_design_doc ) - abstract_design_id = s2b.abstract_translator.extract_toplevel_definition(abstract_design_doc).displayId + abstract_design_id = at.extract_toplevel_definition(abstract_design_doc).displayId plasmid_collection_doc = sbol2.Document() sbh.pull( @@ -23,7 +25,7 @@ def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: backbone_doc, ) - mocloplasmid_list = s2b.abstract_translator.translate_abstract_to_plasmids( + mocloplasmid_list = at.translate_abstract_to_plasmids( abstract_design_doc, plasmid_collection_doc, backbone_doc ) @@ -31,7 +33,7 @@ def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: for mocloPlasmid in mocloplasmid_list: temp_doc = sbol2.Document() mocloPlasmid.definition.copy(temp_doc) - s2b.abstract_translator.copy_sequences( + at.copy_sequences( mocloPlasmid.definition, temp_doc, plasmid_collection_doc @@ -40,7 +42,7 @@ def abstract_design_2_plasmids(abstract_design_uri: str, plasmid_collection_uri: return part_documents, backbone_doc, abstract_design_id -def sbol2build_moclo(part_documents: List[sbol2.Document], backbone_doc: sbol2.document, abstract_design_id: str) -> sbol2.Document: +def sbol2build_moclo(part_documents: List[sbol2.Document], backbone_doc: sbol2.Document, abstract_design_id: str) -> sbol2.Document: assembly_doc = sbol2.Document() assembly_obj = s2b.golden_gate_assembly_plan( f"{abstract_design_id}_assembly_plan", diff --git a/sbs_server/app/views.py b/sbs_server/app/views.py index db5e2b8..7906ce4 100644 --- a/sbs_server/app/views.py +++ b/sbs_server/app/views.py @@ -2,11 +2,11 @@ from flask import Flask, render_template, request, jsonify from flask_cors import CORS from .main import app +from .utils import abstract_design_2_plasmids, sbol2build_moclo import sys import os import json -import sbol2build import tricahue import sbol2 import pudu @@ -131,79 +131,53 @@ def upload_file_from_sbs_post_up(): } return jsonify(sbs_upload_response_dict) -@app.route('/abstract_design_2_plasmids', methods=['POST']) -def abstract_design_2_plasmids(): +@app.route('/upload_assembly', methods=['POST']) +def upload_assembly(): + if 'auth_token' not in request.form: + return jsonify({"error": "Missing SynBioHub Authentication Token"}), 400 + if 'registry_url' not in request.form: + return jsonify({"error": "Missing SynBioHub Registry URL"}), 400 + if 'collection_uri' not in request.form: + return jsonify({"error": "Missing recipient SynBioHub collection URI"}), 400 + if 'abstract_design_uri' not in request.form: return jsonify({"error": "Missing abstract design URI"}), 400 if 'plasmid_collection_uri' not in request.form: return jsonify({"error": "Missing plasmid collection URI"}), 400 if 'plasmid_vector_uri' not in request.form: return jsonify({"error": "Missing plasmid vector URI"}), 400 - - #TODO authtoken and registry errorhandling? - + if 'assembly_protocol' not in request.form: + return jsonify({"error": "Missing assembly protocol type"}), 400 + + auth_token = request.form.get("auth_token") + sbh_registry = request.form.get("registry_url") + recipient_collection_uri = request.form.get("collection_uri") abstract_design_uri = request.form.get("abstract_design_uri") plasmid_collection_uri = request.form.get("plasmid_collection_uri") plasmid_vector_uri = request.form.get("plasmid_vector_uri") - auth_token = request.form.get("auth_token") - - print(abstract_design_uri, plasmid_collection_uri, plasmid_vector_uri, auth_token) - - return 0 - - -@app.route('/sbol_2_build_golden_gate', methods=['POST']) -def sbol_2_build_golden_gate(): - # Error checking in the request - print("request", request.files) - - if 'plasmid_backbone' not in request.files: - return jsonify({"error": "Missing plasmid backbone"}), 400 - if 'insert_parts' not in request.files: - return jsonify({"error": "Missing insert parts"}), 400 - if 'wizard_selections' not in request.form: - return jsonify({"error": "Missing wizard selections"}), 400 - - wizard_selection = request.form.get('wizard_selections') - plasmid_backbone = request.files.get('plasmid_backbone') - insert_parts = request.files.getlist('insert_parts') - - # Parse the json - - wizard_selection_json = json.loads(wizard_selection) - assembly_method = wizard_selection_json.get('formValues').get('assemblyMethod') - - # Check if the assembly method is valid - if assembly_method != 'MoClo': - return jsonify({"error": "Invalid assembly method"}), 400 - - # Get the restriction item - restriction_enzyme = wizard_selection_json.get('formValues').get('restrictionEnzyme') - # code for sbol2build - part_docs = [] - for item in insert_parts: - doc = sbol2.Document() - doc.read(item) - part_docs.append(doc) + sbh = sbol2.PartShop(sbh_registry) + sbh.key = auth_token - bb_doc = sbol2.Document() - bb_doc.read(plasmid_backbone) - - assembly_doc = sbol2.Document() - assembly_obj = sbol2build.golden_gate_assembly_plan('testassem', part_docs, bb_doc, restriction_enzyme, assembly_doc) - try: - composites = assembly_obj.run() + # Run abstract translator to get plasmids + plasmid_documents, vector_doc, design_id = abstract_design_2_plasmids(abstract_design_uri, plasmid_collection_uri, plasmid_vector_uri, sbh) + + # Run plasmids through sbol2build to generate assembly plan + assembly_plan_doc = sbol2build_moclo(plasmid_documents, vector_doc, design_id) + assembly_plan_doc.displayId = f"{design_id}_assembly" - return_string = assembly_doc.writeString() - - # Return the file as a response - return return_string + + sbh_response = sbh.submit( + doc=assembly_plan_doc, + collection=recipient_collection_uri, + overwrite=2 + ) + return sbh_response.text, sbh_response.status_code except ValueError as e: - # catch sbol2build errors and return to frontend return jsonify({"error": str(e)}), 400 + except Exception as e: return jsonify({"error": f"Unexpected server error: {str(e)}"}), 500