Source code for buildcompiler.sbol.transformation
"""SBOL transformation service."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import sbol2
from buildcompiler.domain import IndexedPlasmid, IndexedStrain, MaterialState
[docs]
@dataclass
class TransformationJob:
plasmid: IndexedPlasmid
chassis_identity: str
chassis_display_id: str
source_document: Any
target_document: Any
[docs]
@dataclass
class TransformationSbolResult:
product: IndexedStrain
stage_document: Any
activity_identity: str
artifacts: dict[str, Any] = field(default_factory=dict)
logs: list[str] = field(default_factory=list)
[docs]
class TransformationService:
"""Create SBOL records for transforming a chassis with one plasmid."""
[docs]
def run(self, job: TransformationJob) -> TransformationSbolResult:
plasmid_component = self._plasmid_component(job)
plasmid_impl = self._plasmid_implementation(job, plasmid_component)
chassis_module = self._chassis_module(job)
chassis_impl = self._chassis_implementation(job, chassis_module)
product_display_id = (
f"{job.chassis_display_id}_with_"
f"{job.plasmid.display_id or plasmid_component.displayId}"
)
product_identity = (
f"{job.chassis_identity}/transformed/{plasmid_component.displayId}"
)
transformed_module = sbol2.ModuleDefinition(product_identity)
transformed_module.displayId = product_display_id
transformed_module.name = product_display_id
transformed_module.roles = list(getattr(chassis_module, "roles", []))
inherited_chassis = transformed_module.modules.create("chassis")
inherited_chassis.definition = chassis_module.identity
plasmid_fc = transformed_module.functionalComponents.create(
"engineered_plasmid"
)
plasmid_fc.definition = plasmid_component.identity
transformed_module.wasDerivedFrom = chassis_module.identity
plan = sbol2.Plan(f"{product_identity}/transformation_plan")
plan.displayId = f"{product_display_id}_transformation_plan"
plan.description = "Transform chassis with engineered plasmid."
agent = sbol2.Agent("BuildCompiler")
association = sbol2.Association(f"{product_identity}/association")
association.agent = agent.identity
association.plan = plan.identity
activity = sbol2.Activity(f"{product_identity}/transformation_activity")
activity.displayId = f"transform_{product_display_id}"
activity.name = "Bacterial transformation"
activity.associations.add(association)
activity.usages.add(
sbol2.Usage(
uri="chassis",
entity=chassis_impl.identity,
role=sbol2.SBO_REACTANT,
)
)
activity.usages.add(
sbol2.Usage(
uri="plasmid",
entity=plasmid_impl.identity,
role=sbol2.SBO_REACTANT,
)
)
transformed_impl = sbol2.Implementation(f"{product_identity}/implementation")
transformed_impl.displayId = f"{product_display_id}_implementation"
transformed_impl.built = transformed_module.identity
transformed_impl.wasGeneratedBy = activity.identity
for obj in (
chassis_module,
chassis_impl,
plasmid_component,
plasmid_impl,
transformed_module,
transformed_impl,
plan,
agent,
activity,
):
self._add_if_missing(job.target_document, obj)
metadata = {
"source_stage": "transformation",
"implementation_identity": transformed_impl.identity,
"chassis_identity": chassis_module.identity,
"chassis_implementation_identity": chassis_impl.identity,
"plasmid_identity": plasmid_component.identity,
"plasmid_implementation_identity": plasmid_impl.identity,
"transformation_activity_identity": activity.identity,
}
product = IndexedStrain(
identity=transformed_module.identity,
display_id=product_display_id,
name=transformed_module.name,
state=MaterialState.TRANSFORMED,
roles=list(transformed_module.roles),
metadata=metadata,
sbol_module=transformed_module,
)
return TransformationSbolResult(
product=product,
stage_document=job.target_document,
activity_identity=activity.identity,
artifacts={"transformation": metadata},
logs=[f"Generated transformed strain {transformed_module.identity}."],
)
def _plasmid_component(self, job: TransformationJob) -> sbol2.ComponentDefinition:
component = job.plasmid.sbol_component or job.source_document.find(
job.plasmid.identity
)
if isinstance(component, sbol2.ComponentDefinition):
return component
fallback = sbol2.ComponentDefinition(job.plasmid.identity)
fallback.displayId = (
job.plasmid.display_id or job.plasmid.identity.rsplit("/", 1)[-1]
)
fallback.name = job.plasmid.name
fallback.roles = list(job.plasmid.roles)
return fallback
def _plasmid_implementation(
self, job: TransformationJob, component: sbol2.ComponentDefinition
) -> sbol2.Implementation:
impl_identity = job.plasmid.metadata.get("implementation_identity")
implementation = (
job.source_document.find(impl_identity) if impl_identity else None
)
if isinstance(implementation, sbol2.Implementation):
return implementation
for candidate in job.source_document.implementations:
if (
isinstance(candidate, sbol2.Implementation)
and candidate.built == component.identity
):
return candidate
implementation = sbol2.Implementation(f"{component.identity}/implementation")
implementation.displayId = f"{component.displayId}_implementation"
implementation.built = component.identity
return implementation
def _chassis_module(self, job: TransformationJob) -> sbol2.ModuleDefinition:
module = job.source_document.find(job.chassis_identity)
if isinstance(module, sbol2.ModuleDefinition):
return module
module = sbol2.ModuleDefinition(job.chassis_identity)
module.displayId = job.chassis_display_id
module.name = job.chassis_display_id
return module
def _chassis_implementation(
self, job: TransformationJob, module: sbol2.ModuleDefinition
) -> sbol2.Implementation:
for candidate in job.source_document.implementations:
if (
isinstance(candidate, sbol2.Implementation)
and candidate.built == module.identity
):
return candidate
implementation = sbol2.Implementation(f"{module.identity}/implementation")
implementation.displayId = f"{module.displayId}_implementation"
implementation.built = module.identity
return implementation
def _add_if_missing(self, document: sbol2.Document, obj: Any) -> None:
if document.find(obj.identity) is None:
document.add(obj)