├── moclo-ytk ├── moclo │ ├── kits │ │ ├── ytk.version │ │ └── __init__.py │ └── registry │ │ └── ytk.py ├── MANIFEST.in ├── registry │ └── ytk │ │ └── README.rst ├── COPYING ├── setup.py ├── CHANGELOG ├── README.md └── setup.cfg ├── docs ├── source │ ├── kits │ │ ├── ytk │ │ │ ├── .gitignore │ │ │ ├── part.svg.gz │ │ │ └── index.rst │ │ ├── plant │ │ │ └── index.rst │ │ ├── cidar │ │ │ └── index.rst │ │ ├── ecoflex │ │ │ └── index.rst │ │ └── moclo │ │ │ └── index.rst │ ├── examples │ │ ├── .gitignore │ │ └── index.rst │ ├── changes │ │ ├── .gitignore │ │ └── index.rst │ ├── api │ │ ├── record.rst │ │ ├── parts.rst │ │ ├── registry.rst │ │ ├── vectors.rst │ │ ├── errors.rst │ │ ├── modules.rst │ │ └── index.rst │ ├── theory │ │ ├── index.rst │ │ └── typed.rst │ ├── _static │ │ └── bootstrap-math.css │ ├── _scripts │ │ ├── registries.py │ │ └── ytk_parts.py │ ├── about.rst │ ├── concepts │ │ └── definitions.rst │ ├── install.rst │ └── index.rst ├── requirements.txt ├── Makefile └── make.bat ├── moclo-cidar ├── moclo │ ├── kits │ │ └── cidar.version │ └── registry │ │ └── cidar.py ├── MANIFEST.in ├── COPYING ├── setup.py ├── CHANGELOG ├── README.md └── setup.cfg ├── moclo-moclo ├── moclo │ └── kits │ │ └── moclo.version ├── MANIFEST.in ├── CHANGELOG ├── COPYING ├── setup.py ├── README.md └── setup.cfg ├── moclo-plant ├── moclo │ ├── kits │ │ ├── plant.version │ │ └── plant.py │ └── registry │ │ └── plant.py ├── MANIFEST.in ├── CHANGELOG ├── COPYING ├── setup.py ├── README.md └── setup.cfg ├── moclo-ecoflex ├── moclo │ ├── kits │ │ ├── ecoflex.version │ │ └── ecoflex.py │ └── registry │ │ └── ecoflex.py ├── MANIFEST.in ├── COPYING ├── setup.py ├── CHANGELOG ├── README.md └── setup.cfg ├── tests ├── data │ ├── cases │ │ ├── .gitignore │ │ ├── ytk_device.tar.xz.enc │ │ └── ytk_integration_vector.tar.xz │ └── README.rst ├── requirements.txt ├── test_utils.py ├── __init__.py ├── test_regex.py ├── test_plant.py ├── test_ecoflex.py ├── test_doctest.py ├── test_ytk.py ├── test_assembly.py ├── test_core.py ├── test_record.py ├── test_cidar.py ├── _utils.py └── test_registry.py ├── moclo ├── MANIFEST.in ├── setup.py ├── moclo │ ├── registry │ │ ├── __init__.py │ │ ├── _utils.py │ │ └── base.py │ ├── kits │ │ └── __init__.py │ ├── __init__.py │ ├── _impl.py │ ├── core │ │ ├── __init__.py │ │ ├── _utils.py │ │ ├── _structured.py │ │ ├── parts.py │ │ ├── _assembly.py │ │ ├── modules.py │ │ └── vectors.py │ ├── _utils.py │ ├── errors.py │ └── regex.py ├── COPYING ├── setup.cfg ├── README.md └── CHANGELOG ├── notebook └── ytk │ ├── misc │ ├── build.txt │ ├── libs.txt │ ├── requirements.txt │ └── glib-patch.sh │ ├── Dockerfile │ └── YTK Protocol.ipynb ├── .github ├── workflows │ ├── requirements.txt │ ├── before-deploy.py │ ├── test.yml │ └── package.yml └── dependabot.yml ├── setup.cfg ├── COPYING ├── .gitignore └── README.md /moclo-ytk/moclo/kits/ytk.version: -------------------------------------------------------------------------------- 1 | 0.4.0 2 | -------------------------------------------------------------------------------- /docs/source/kits/ytk/.gitignore: -------------------------------------------------------------------------------- 1 | type*.svg 2 | -------------------------------------------------------------------------------- /moclo-cidar/moclo/kits/cidar.version: -------------------------------------------------------------------------------- 1 | 0.4.0 2 | -------------------------------------------------------------------------------- /moclo-moclo/moclo/kits/moclo.version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /moclo-plant/moclo/kits/plant.version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /moclo-ecoflex/moclo/kits/ecoflex.version: -------------------------------------------------------------------------------- 1 | 0.3.1 2 | -------------------------------------------------------------------------------- /docs/source/examples/.gitignore: -------------------------------------------------------------------------------- 1 | __MACOSX 2 | plasmids 3 | -------------------------------------------------------------------------------- /tests/data/cases/.gitignore: -------------------------------------------------------------------------------- 1 | ytk_device.tar.xz 2 | *.fa 3 | -------------------------------------------------------------------------------- /moclo/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | include CHANGELOG 4 | -------------------------------------------------------------------------------- /docs/source/changes/.gitignore: -------------------------------------------------------------------------------- 1 | # Changelog files obtained from each submodule 2 | moclo*.rst 3 | -------------------------------------------------------------------------------- /moclo-ecoflex/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | recursive-include registry *.gb 4 | -------------------------------------------------------------------------------- /docs/source/kits/ytk/part.svg.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/althonos/moclo/HEAD/docs/source/kits/ytk/part.svg.gz -------------------------------------------------------------------------------- /moclo/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import setuptools 5 | 6 | setuptools.setup() 7 | -------------------------------------------------------------------------------- /moclo-moclo/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | include CHANGELOG 4 | recursive-include registry *.gb 5 | -------------------------------------------------------------------------------- /moclo-plant/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | include CHANGELOG 4 | recursive-include registry *.gb 5 | -------------------------------------------------------------------------------- /moclo-ytk/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | include CHANGELOG 4 | recursive-include registry *.gb 5 | -------------------------------------------------------------------------------- /moclo-cidar/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.rst 3 | include CHANGELOG 4 | recursive-include registry *.gb 5 | -------------------------------------------------------------------------------- /tests/data/cases/ytk_device.tar.xz.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/althonos/moclo/HEAD/tests/data/cases/ytk_device.tar.xz.enc -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | backports.lzma ; python_version < '3.3' 2 | contexter ~=0.1.0 3 | fs ~=2.3.0 4 | fs.archive ~=0.7.3 5 | -------------------------------------------------------------------------------- /notebook/ytk/misc/build.txt: -------------------------------------------------------------------------------- 1 | git 2 | nodejs 3 | npm 4 | ca-certificates 5 | gcc 6 | make 7 | cmake 8 | g++ 9 | gfortran 10 | -------------------------------------------------------------------------------- /docs/source/changes/index.rst: -------------------------------------------------------------------------------- 1 | Changelogs 2 | ========== 3 | 4 | .. toctree:: 5 | :glob: 6 | :maxdepth: 1 7 | 8 | moclo* 9 | -------------------------------------------------------------------------------- /tests/data/cases/ytk_integration_vector.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/althonos/moclo/HEAD/tests/data/cases/ytk_integration_vector.tar.xz -------------------------------------------------------------------------------- /notebook/ytk/misc/libs.txt: -------------------------------------------------------------------------------- 1 | musl-dev 2 | libpng-dev 3 | freetype-dev 4 | libxml2-dev 5 | libxslt-dev 6 | libstdc++ 7 | linux-headers 8 | zeromq-dev 9 | -------------------------------------------------------------------------------- /.github/workflows/requirements.txt: -------------------------------------------------------------------------------- 1 | codacy-coverage 2 | codecov 3 | coverage 4 | docutils 5 | green 6 | Pygments 7 | setuptools 8 | twine 9 | wheel 10 | -------------------------------------------------------------------------------- /docs/source/kits/plant/index.rst: -------------------------------------------------------------------------------- 1 | Plant Parts Kit 2 | =============== 3 | 4 | .. currentmodule:: moclo.kits.plant 5 | 6 | .. automodule:: moclo.kits.plant 7 | -------------------------------------------------------------------------------- /notebook/ytk/misc/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | ipywidgets 3 | jupyterlab 4 | qgrid 5 | biopython 6 | pandas 7 | fs 8 | six 9 | moclo 10 | moclo-ytk 11 | -------------------------------------------------------------------------------- /moclo/moclo/registry/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 # noqa: D104 2 | """Namespace package for MoClo plasmid registries. 3 | """ 4 | __path__ = __import__("pkgutil").extend_path(__path__, __name__) 5 | -------------------------------------------------------------------------------- /moclo/moclo/kits/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 # noqa: D104 2 | """Namespace package for concrete MoClo implementations. 3 | """ 4 | __path__ = __import__("pkgutil").extend_path(__path__, __name__) 5 | -------------------------------------------------------------------------------- /moclo-ytk/moclo/kits/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 # noqa: D104 2 | """Namespace package for concrete MoClo implementations. 3 | """ 4 | __path__ = __import__("pkgutil").extend_path(__path__, __name__) 5 | -------------------------------------------------------------------------------- /docs/source/api/record.rst: -------------------------------------------------------------------------------- 1 | Record 2 | ====== 3 | 4 | .. currentmodule:: moclo.record 5 | .. automodule:: moclo.record 6 | 7 | .. autoclass:: CircularRecord(SeqRecord) 8 | :members: 9 | :special-members: 10 | -------------------------------------------------------------------------------- /moclo/moclo/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import as _ 3 | from __future__ import unicode_literals as _ 4 | 5 | __author__ = "Martin Larralde " 6 | __version__ = "0.4.7" 7 | -------------------------------------------------------------------------------- /docs/source/api/parts.rst: -------------------------------------------------------------------------------- 1 | Parts 2 | ===== 3 | 4 | .. automodule:: moclo.core.parts 5 | 6 | Abstract 7 | -------- 8 | 9 | .. autoclass:: AbstractPart(object) 10 | :members: 11 | :inherited-members: 12 | :special-members: __init__ 13 | -------------------------------------------------------------------------------- /docs/source/examples/index.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | This page contains examples in Python code, generated from Jupyter notebooks 5 | with `nbsphinx `_. 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | Assembling the YTK integration vector 12 | -------------------------------------------------------------------------------- /docs/source/api/registry.rst: -------------------------------------------------------------------------------- 1 | Registry 2 | ======== 3 | 4 | .. automodule:: moclo.registry.base 5 | .. currentmodule:: moclo.registry.base 6 | 7 | 8 | Base class 9 | ---------- 10 | 11 | .. autoclass:: AbstractRegistry 12 | 13 | 14 | Implementations 15 | --------------- 16 | 17 | .. autoclass:: CombinedRegistry 18 | :members: 19 | :special-members: __init__ 20 | 21 | .. autoclass:: EmbeddedRegistry 22 | :members: 23 | :special-members: __init__ 24 | -------------------------------------------------------------------------------- /docs/source/theory/index.rst: -------------------------------------------------------------------------------- 1 | Descriptive Theory 2 | ================== 3 | 4 | This section introduces the theory that was developed to support the software 5 | implementation of the modular cloning logic. It introduces mathematical 6 | definitions of biological concepts, relying on particular on 7 | `formal language theory `_. 8 | 9 | 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | definitions 15 | standard 16 | typed 17 | -------------------------------------------------------------------------------- /tests/data/README.rst: -------------------------------------------------------------------------------- 1 | Test Data 2 | ========= 3 | 4 | This folder contains test data obtain from different sources: 5 | 6 | * ``ytk.tsv.xz`` is a compressed CSV version of the ``YTK_Parts.xls`` file that 7 | comes with the YTK GenBank files. 8 | * ``ytk-multigene.tsv.xz.enc`` contains encrypted compressed plasmid sequences 9 | of a multigene assembly, provided by `Sebastián Sosa Carrillo `_ 10 | but not available for public use. 11 | -------------------------------------------------------------------------------- /docs/source/_static/bootstrap-math.css: -------------------------------------------------------------------------------- 1 | .math-definition { 2 | background-color: #e1f2de; 3 | border-color: #bfe6cc; 4 | color: #3b965a; 5 | } 6 | 7 | .math-property { 8 | background-color: #f2dede; 9 | border-color: #eed3d7; 10 | color: #b94a48; 11 | } 12 | 13 | .math-demo { 14 | background-color: #f2e6de; 15 | border-color: #eeddd3; 16 | color: #b98448; 17 | } 18 | 19 | /* .math-demo { 20 | background-color: #ebdef2; 21 | border-color: #e4d3ee; 22 | color: #8648b9; 23 | } */ 24 | -------------------------------------------------------------------------------- /docs/source/_scripts/registries.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | import sys 4 | import subprocess 5 | 6 | DOCSRC_DIR = os.path.abspath(os.path.join(__file__, '..', '..')) 7 | _EXT_DIR = os.path.abspath(os.path.join(DOCSRC_DIR, '..', '..', 'moclo-{}')) 8 | 9 | def build_registries(name): 10 | cmd = subprocess.Popen( 11 | [sys.executable, 'setup.py', 'build_ext', '--inplace'], 12 | cwd=_EXT_DIR.format(name), 13 | stdout=subprocess.PIPE, 14 | stderr=subprocess.PIPE, 15 | ) 16 | cmd.communicate() 17 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # direct moclo dependencies 2 | biopython ~=1.71 3 | property-cached ~=1.4 4 | configparser ; python_version < '3' 5 | fs ~=2.4.0 6 | semantic-version ~=2.6 7 | setuptools >=30.3 8 | six ~=1.10 9 | typing ~=3.6 ; python_version < '3.6' 10 | 11 | # sphinx development version (1.8) and bootstrap theme 12 | sphinx-bootstrap-theme ~=0.7 13 | Sphinx ~=4.0 14 | 15 | # nbsphinx dependencies 16 | nbsphinx 17 | ipykernel 18 | IPython 19 | 20 | # examples dependencies 21 | requests 22 | dna_features_viewer 23 | matplotlib 24 | -------------------------------------------------------------------------------- /moclo-moclo/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | 10 | v0.1.0_ - 2018-07-12 11 | -------------------- 12 | 13 | Initial public release. 14 | 15 | 16 | .. _Unreleased: https://github.com/althonos/moclo/compare/ig/v0.1.0...HEAD 17 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...ig/v0.1.0 18 | -------------------------------------------------------------------------------- /moclo-plant/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | 10 | v0.1.0_ - 2018-07-12 11 | -------------------- 12 | 13 | Initial public release. 14 | 15 | 16 | .. _Unreleased: https://github.com/althonos/moclo/compare/ig/v0.1.0...HEAD 17 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...ig/v0.1.0 18 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import collections.abc 6 | import unittest 7 | 8 | from moclo._utils import isabstract 9 | 10 | 11 | class TestIsAbstract(unittest.TestCase): 12 | def test_abstract_method(self): 13 | self.assertTrue(isabstract(collections.abc.Iterable)) 14 | self.assertFalse(isabstract(int)) 15 | 16 | def test_abstract_attribute(self): 17 | class Abstract(object): 18 | thingy = NotImplemented 19 | 20 | self.assertTrue(isabstract(Abstract)) 21 | -------------------------------------------------------------------------------- /moclo/moclo/_impl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import importlib 6 | 7 | 8 | def _import_from(*names): 9 | for name in names: 10 | if name is None: 11 | return name 12 | try: 13 | return importlib.import_module(name) 14 | except ImportError: 15 | pass 16 | raise ImportError("no module found among: {}".format(", ".join(names))) 17 | 18 | 19 | bz2 = _import_from("bz2file", "bz2") 20 | json = _import_from("hyperjson", "ujson", "yajl", "rapidjson", "simplejson", "json") 21 | ssl = _import_from("ssl", None) 22 | -------------------------------------------------------------------------------- /docs/source/api/vectors.rst: -------------------------------------------------------------------------------- 1 | Vectors 2 | ======= 3 | 4 | .. automodule:: moclo.core.vectors 5 | 6 | 7 | Abstract 8 | -------- 9 | 10 | .. autoclass:: AbstractVector(object) 11 | :members: 12 | :special-members: __init__ 13 | 14 | 15 | Level -1 16 | -------- 17 | 18 | .. autoclass:: EntryVector(AbstractVector) 19 | :members: 20 | :special-members: __init__ 21 | 22 | 23 | Level 0 24 | ------- 25 | 26 | .. autoclass:: CassetteVector(AbstractVector) 27 | :members: 28 | :special-members: __init__ 29 | 30 | 31 | Level 1 32 | ------- 33 | 34 | .. autoclass:: DeviceVector(AbstractVector) 35 | :members: 36 | :special-members: __init__ 37 | -------------------------------------------------------------------------------- /docs/source/api/errors.rst: -------------------------------------------------------------------------------- 1 | Errors 2 | ====== 3 | 4 | .. automodule:: moclo.errors 5 | .. currentmodule:: moclo.errors 6 | 7 | 8 | Base classes 9 | ------------ 10 | 11 | .. autoclass:: MocloError(Exception) 12 | .. autoclass:: AssemblyError(MocloError, RuntimeError) 13 | .. autoclass:: AssemblyWarning(MocloError, Warning) 14 | 15 | 16 | Errors 17 | ------ 18 | 19 | .. autoclass:: DuplicateModules(AssemblyError) 20 | .. autoclass:: InvalidSequence(MocloError, ValueError) 21 | .. autoclass:: IllegalSite(InvalidSequence) 22 | .. autoclass:: MissingModule(AssemblyError) 23 | 24 | 25 | Warnings 26 | -------- 27 | 28 | .. autoclass:: UnusedModules(AssemblyWarning) 29 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = moclo 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /moclo-ytk/registry/ytk/README.rst: -------------------------------------------------------------------------------- 1 | Yeast ToolKit registry 2 | ====================== 3 | 4 | Records 5 | ------- 6 | 7 | Records were amalgamated from 2 sources: 8 | 9 | * The YTK zip archive from the *Protocols & Resources* page of the YTK Addgene page 10 | * Each individual Addgene plasmid page 11 | 12 | The two records contain different annotations that have been merged into a single 13 | file, so there may be some duplications in the annotations. If so, don't hesitate 14 | to `open an issue `_. 15 | 16 | Additional annotations 17 | ---------------------- 18 | 19 | * ``CamR`` and ``CmR`` were merged into a single feature 20 | * ``CmR`` have received additional ``db_xref`` qualifiers 21 | -------------------------------------------------------------------------------- /docs/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | Modules 2 | ======= 3 | 4 | .. automodule:: moclo.core.modules 5 | 6 | Abstract 7 | -------- 8 | 9 | .. autoclass:: AbstractModule(object) 10 | :members: 11 | :inherited-members: 12 | :special-members: __init__ 13 | 14 | 15 | Level -1 16 | -------- 17 | 18 | .. autoclass:: Product(AbstractModule) 19 | :members: 20 | :special-members: __init__ 21 | 22 | 23 | Level 0 24 | ------- 25 | 26 | .. autoclass:: Entry(AbstractModule) 27 | :members: 28 | :special-members: __init__ 29 | 30 | 31 | Level 1 32 | ------- 33 | 34 | .. autoclass:: Cassette(AbstractModule) 35 | :members: 36 | :special-members: __init__ 37 | 38 | 39 | Level 2 40 | ------- 41 | 42 | .. autoclass:: Device(AbstractModule) 43 | :members: 44 | :special-members: __init__ 45 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import os 6 | import sys 7 | 8 | # Patch the PYTHONPATH to find the base moclo package 9 | proj = os.path.abspath(os.path.join(__file__, "..", "..")) 10 | sys.path.insert(0, os.path.join(proj, "moclo")) 11 | 12 | # Load the kits namespace and add additional plugins packages 13 | import moclo.kits # noqa: E402 14 | import moclo.registry # noqa: E402 15 | 16 | # Load extensions 17 | for extension in ["cidar", "ytk", "ecoflex", "moclo", "plant"]: 18 | extension_dir = os.path.join(proj, "moclo-{}".format(extension)) 19 | moclo.kits.__path__.append(os.path.join(extension_dir, "moclo", "kits")) 20 | moclo.registry.__path__.append(os.path.join(extension_dir, "moclo", "registry")) 21 | -------------------------------------------------------------------------------- /notebook/ytk/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:alpine 2 | 3 | ADD *.py *.ipynb nb/ 4 | ADD misc/* misc/ 5 | 6 | RUN apk add --no-cache --update $(cat misc/libs.txt) \ 7 | && apk add --no-cache --update --virtual=.build-dependencies $(cat misc/build.txt) \ 8 | && pip install --no-cache-dir -r misc/requirements.txt \ 9 | && jupyter nbextension enable --py widgetsnbextension \ 10 | && jupyter serverextension enable --py jupyterlab \ 11 | && jupyter labextension install @jupyter-widgets/jupyterlab-manager qgrid \ 12 | && /misc/glib-patch.sh \ 13 | && apk del .build-dependencies 14 | 15 | ENV LANG=C.UTF-8 16 | EXPOSE 8888 17 | CMD jupyter lab \ 18 | --ip=0.0.0.0 \ 19 | --port=8888 \ 20 | --no-browser \ 21 | --NotebookApp.token='' \ 22 | --notebook-dir=/nb \ 23 | --allow-root \ 24 | /nb/YTK\ Protocol.ipynb 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [coverage:report] 2 | show_missing = true 3 | exclude_lines = 4 | pragma: no cover 5 | if typing.TYPE_CHECKING: 6 | @abc.abstractmethod 7 | @abc.abstractproperty 8 | raise NotImplementedError 9 | return NotImplemented 10 | 11 | [green] 12 | file-pattern = test_*.py 13 | verbose = 2 14 | no-skip-report = true 15 | quiet-stdout = true 16 | run-coverage = true 17 | 18 | [pydocstyle] 19 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 20 | match = (?!test)(?!setup)[^\._].*\.py 21 | inherit = false 22 | ignore = D200, D203, D213, D406, D407 # Google conventions 23 | 24 | [flake8] 25 | max-line-length = 99 26 | doctests = True 27 | ignore = D200, D203, D213, D406, D407, Q000 # Google conventions 28 | exclude = 29 | .git 30 | .eggs 31 | ci/ 32 | scripts/ 33 | notebook/ 34 | tests/ 35 | docs/ 36 | */build/ 37 | */setup.py 38 | -------------------------------------------------------------------------------- /notebook/ytk/misc/glib-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e -x 4 | 5 | wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub 6 | wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk 7 | wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-i18n-2.23-r3.apk 8 | wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-bin-2.23-r3.apk 9 | apk add --no-cache glibc-2.23-r3.apk glibc-bin-2.23-r3.apk glibc-i18n-2.23-r3.apk 10 | rm "/etc/apk/keys/sgerrand.rsa.pub" 11 | /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 C.UTF-8 || true 12 | echo "export LANG=C.UTF-8" > /etc/profile.d/locale.sh 13 | ln -s /usr/include/locale.h /usr/include/xlocale.h 14 | rm glibc-2.23-r3.apk glibc-bin-2.23-r3.apk glibc-i18n-2.23-r3.apk 15 | -------------------------------------------------------------------------------- /moclo-plant/moclo/registry/plant.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Icon Genetics ToolKit sequences registry. 3 | 4 | Sequences were obtained from the MoClo plasmid files distributed with the kit 5 | in a zip archive, available under the *Protocol & Resources* tab of the 6 | Icon Genetics MoClo repository. 7 | 8 | """ 9 | from __future__ import absolute_import 10 | from __future__ import unicode_literals 11 | 12 | import six 13 | 14 | from ..kits import moclo, plant 15 | from .base import EmbeddedRegistry 16 | from ._utils import find_resistance 17 | 18 | 19 | class PlantRegistry(EmbeddedRegistry): 20 | 21 | _module = __name__ 22 | _file = "plant.tar.gz" 23 | 24 | _types = {} 25 | 26 | def _load_entity(self, record): 27 | if record.id in self._types: 28 | return self._types[record.id](record) 29 | return moclo.MoCloPart.characterize(record) 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=moclo 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/moclo" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: pip 10 | directory: "/moclo-ytk" 11 | schedule: 12 | interval: daily 13 | time: "04:00" 14 | open-pull-requests-limit: 10 15 | - package-ecosystem: pip 16 | directory: "/moclo-ecoflex" 17 | schedule: 18 | interval: daily 19 | time: "04:00" 20 | open-pull-requests-limit: 10 21 | - package-ecosystem: pip 22 | directory: "/moclo-cidar" 23 | schedule: 24 | interval: daily 25 | time: "04:00" 26 | open-pull-requests-limit: 10 27 | - package-ecosystem: pip 28 | directory: "/moclo-ig" 29 | schedule: 30 | interval: daily 31 | time: "04:00" 32 | open-pull-requests-limit: 10 33 | - package-ecosystem: pip 34 | directory: "/moclo-gb3" 35 | schedule: 36 | interval: daily 37 | time: "04:00" 38 | open-pull-requests-limit: 10 39 | -------------------------------------------------------------------------------- /moclo/moclo/core/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # noqa: F401 3 | """MoClo object model and abstract classes. 4 | 5 | This module contains base classes that implement the Modular Cloning logic. 6 | All of these classes are abstract, as they need a DNA sequence structure to 7 | be specified. Implementations can be found in the ``moclo.kits`` namespace 8 | once installed. The MoClo system relies on the Golden Gate assembly combined 9 | to clever sequence design to create genetic constructs in a simple and 10 | deterministic way. 11 | """ 12 | from __future__ import absolute_import 13 | 14 | from .parts import AbstractPart 15 | from .modules import AbstractModule, Cassette, Entry, Device, Product 16 | from .vectors import AbstractVector, CassetteVector, EntryVector, DeviceVector 17 | 18 | __all__ = [ 19 | "AbstractPart", 20 | "AbstractModule", 21 | "AbstractVector", 22 | "Cassette", 23 | "CassetteVector", 24 | "Entry", 25 | "EntryVector", 26 | "Device", 27 | "DeviceVector", 28 | "Product", 29 | ] 30 | -------------------------------------------------------------------------------- /.github/workflows/before-deploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #coding: utf-8 3 | 4 | import glob 5 | import os 6 | import re 7 | import subprocess 8 | import sys 9 | 10 | ref = os.getenv('GITHUB_REF') 11 | if not ref.startswith("refs/tags/"): 12 | raise RuntimeError("not a tagged commit: {!r}".format(ref)) 13 | 14 | if ref.count("/") == 2: 15 | libdir = 'moclo' 16 | elif ref.count("/") == 3: 17 | lib = tag.split('/')[-2] 18 | libdir = 'moclo-{}'.format(lib) 19 | else: 20 | raise RuntimeError("Could not recognize ref: {!r}".format(ref)) 21 | 22 | args = [ 23 | sys.executable, 24 | 'setup.py', 25 | 'sdist', 26 | '-d', 27 | os.path.join(os.pardir, 'dist'), 28 | 'bdist_wheel', 29 | '-d', 30 | os.path.join(os.pardir, 'dist'), 31 | ] 32 | 33 | print("Deploying", libdir) 34 | subprocess.Popen(args, cwd=libdir).communicate() 35 | 36 | wheel = next(glob.iglob(os.path.join('dist', '*.whl'))) 37 | new_wheel = re.sub('cp38-cp38m-linux_x86_64', 'py2.py3-none-any', wheel) 38 | 39 | os.rename(wheel, new_wheel) 40 | -------------------------------------------------------------------------------- /moclo/moclo/core/_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from Bio.SeqFeature import SeqFeature, FeatureLocation 6 | 7 | 8 | def cutter_check(cutter, name): 9 | if cutter is NotImplemented: 10 | raise NotImplementedError("{} does not declare a cutter".format(name)) 11 | elif cutter.is_blunt(): 12 | raise ValueError("cannot use a blunt cutter for Golden Gate") 13 | elif cutter.is_unknown(): 14 | raise ValueError("cannot use an unknown cutter for Golden Gate") 15 | 16 | 17 | def add_as_source(src_record, dst_record, location=None): 18 | quals = { 19 | "organism": "synthetic DNA construct", 20 | "mol_type": "other DNA", 21 | "plasmid": src_record.id, 22 | "label": "source: {}".format(src_record.id), 23 | } 24 | location = location or FeatureLocation(0, len(dst_record)) 25 | feat = SeqFeature(location, type="source", qualifiers=quals) 26 | dst_record.features.append(feat) 27 | return dst_record 28 | -------------------------------------------------------------------------------- /moclo/moclo/registry/_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .._utils import isabstract 4 | 5 | _ANTIBIOTICS = { 6 | "KanR": "Kanamycin", 7 | "CamR": "Chloramphenicol", 8 | "CmR": "Chloramphenicol", 9 | "KnR": "Kanamycin", 10 | "AmpR": "Ampicillin", 11 | "SmR": "Spectinomycin", 12 | "SpecR": "Spectinomycin", 13 | } 14 | 15 | 16 | def find_resistance(record): 17 | """Infer the antibiotics resistance of the given record. 18 | 19 | Arguments: 20 | record (`~Bio.SeqRecord.SeqRecord`): an annotated sequence. 21 | 22 | Raises: 23 | RuntimeError: when there's not exactly one resistance cassette. 24 | 25 | """ 26 | for feature in record.features: 27 | labels = set(feature.qualifiers.get("label", [])) 28 | cassettes = labels.intersection(_ANTIBIOTICS) 29 | if len(cassettes) > 1: 30 | raise RuntimeError("multiple resistance cassettes detected") 31 | elif len(cassettes) == 1: 32 | return _ANTIBIOTICS.get(cassettes.pop()) 33 | raise RuntimeError("could not find the resistance of '{}'".format(record.id)) 34 | -------------------------------------------------------------------------------- /tests/test_regex.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from Bio.Seq import Seq 8 | 9 | from moclo.regex import DNARegex 10 | 11 | 12 | class TestDNARegex(unittest.TestCase): 13 | def test_type(self): 14 | dr = DNARegex("NN") 15 | self.assertRaises(TypeError, dr.search, "ATGC") 16 | 17 | def test_sequence_match(self): 18 | dr = DNARegex("AA(NN)") 19 | match = dr.search(Seq("ATGCAAGCAATA")) 20 | self.assertIsNotNone(match) 21 | self.assertEqual(match.start(), 4) 22 | self.assertEqual(match.end(), 8) 23 | self.assertEqual(match.span(1), (6, 8)) 24 | self.assertEqual(match.group(1), Seq("GC")) 25 | 26 | def test_plasmid_match(self): 27 | dr = DNARegex("AA(NN)") 28 | match = dr.search(Seq("ATGCAGCATA"), linear=False) 29 | self.assertIsNotNone(match) 30 | self.assertEqual(match.span(0), (9, 13)) 31 | self.assertEqual(match.span(1), (11, 13)) 32 | self.assertEqual(match.group(1), Seq("TG")) 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo-cidar/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo-moclo/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo-plant/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo-ytk/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /moclo-ecoflex/COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Martin Larralde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/source/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | Authors 5 | ------- 6 | 7 | ``moclo`` is developped and maintained by: 8 | 9 | 10 | +-------------------------------------------------+ 11 | | | **Martin Larralde** | 12 | | | Graduate student, Biology department | 13 | | | École Normale Supérieure Paris Saclay | 14 | | | martin.larralde@ens-paris-saclay.fr | 15 | +-------------------------------------------------+ 16 | 17 | This library was developped during a summer internship at **Institut Pasteur**, 18 | under the supervision of: 19 | 20 | +-------------------------------------------------+ 21 | | | **François Bertaux** | 22 | | | Reserach Engineer, InBio Unit | 23 | | | Inria / Institut Pasteur | 24 | | | francois.bertaux@pasteur.fr | 25 | +-------------------------------------------------+ 26 | | | **Grégory Batt** | 27 | | | Senior Scientist, Head of InBio Unit | 28 | | | Inria / Institut Pasteur | 29 | | | gregory.batt@inria.fr | 30 | +-------------------------------------------------+ 31 | 32 | 33 | License 34 | ------- 35 | 36 | This project is licensed under the `MIT License `_. 37 | -------------------------------------------------------------------------------- /tests/test_plant.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from moclo.kits import moclo 8 | from moclo.registry.plant import PlantRegistry 9 | 10 | from ._utils import AssemblyTestCase, PartsMetaCase, build_registries 11 | 12 | # --- Test Suite Metaclass --------------------------------------------------- 13 | 14 | _Meta = PartsMetaCase("Plant", PlantRegistry, __name__) 15 | 16 | 17 | # --- Test Plant Parts ------------------------------------------------------- 18 | 19 | TestMoCloPro = _Meta(moclo.MoCloPro, "Pro") 20 | TestMoClo5U = _Meta(moclo.MoClo5U, "5U") 21 | TestMoClo5Uf = _Meta(moclo.MoClo5Uf, "5Uf") 22 | TestMoCloNTag = _Meta(moclo.MoCloNTag, "NTag") 23 | TestMoCloPro5U = _Meta(moclo.MoCloPro5U, "Pro5U") 24 | TestMoCloPro5Uf = _Meta(moclo.MoCloPro5Uf, "Pro5Uf") 25 | TestMoCloCDS1 = _Meta(moclo.MoCloCDS1, "CDS1") 26 | TestMoCloCDS1ns = _Meta(moclo.MoCloCDS1ns, "CDS1ns") 27 | TestMoCloSP = _Meta(moclo.MoCloSP, "SP") 28 | TestMoCloCDS2 = _Meta(moclo.MoCloCDS2, "CDS2") 29 | TestMoCloCDS2ns = _Meta(moclo.MoCloCDS2ns, "CDS2ns") 30 | TestMoCloCTag = _Meta(moclo.MoCloCTag, "CTag") 31 | TestMoClo3U = _Meta(moclo.MoClo3U, "MoClo3U") 32 | TestMoCloTer = _Meta(moclo.MoCloTer, "MoCloTer") 33 | TestMoClo3UTer = _Meta(moclo.MoClo3UTer, "MoClo3UTer") 34 | TestMoCloGene = _Meta(moclo.MoCloGene, "MoCloGene") 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | 9 | test: 10 | name: Test 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: 15 | - '3.6' 16 | - '3.7' 17 | - '3.8' 18 | - '3.9' 19 | - '3.10' 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Setup Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Update pip 30 | run: python -m pip install -U pip wheel setuptools 31 | - name: Install Python requirements 32 | run: python -m pip install -r .github/workflows/requirements.txt 33 | - name: Install packages 34 | run: pip install ./moclo ./moclo-* 35 | - name: Install test requirements 36 | run: python -m pip install -r tests/requirements.txt 37 | - name: Test with coverage 38 | run: python -m coverage run -m unittest discover -vv 39 | - name: Upload to Codecov 40 | uses: codecov/codecov-action@v1 41 | with: 42 | flags: ${{ matrix.python-version }} 43 | name: test-python-${{ matrix.python-version }} 44 | fail_ci_if_error: true 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | -------------------------------------------------------------------------------- /moclo-cidar/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import json 5 | import glob 6 | import os 7 | import setuptools 8 | import sys 9 | import tarfile 10 | 11 | from setuptools.command.build_ext import build_ext as _build_ext 12 | 13 | 14 | class Registry(setuptools.Extension): 15 | def __init__(self, name): 16 | directory = os.path.join("registry", name) 17 | sources = glob.glob(os.path.join(directory, "*.gb")) 18 | setuptools.Extension.__init__(self, name, sources) 19 | 20 | 21 | class build_ext(_build_ext): 22 | def get_ext_filename(self, ext_name): 23 | return os.path.join(*ext_name.split(".")) + ".tar.gz" 24 | 25 | def build_extension(self, ext): 26 | # find directories 27 | registry = [] 28 | gb_dir = os.path.dirname(ext.sources[0]) 29 | dst_file = self.get_ext_fullpath(ext.name) 30 | dst_dir = os.path.dirname(dst_file) 31 | # copy sequences 32 | self.mkpath(dst_dir) 33 | with tarfile.open(dst_file, mode="w:gz") as tar: 34 | for gb_file in sorted(ext.sources): 35 | arcname, _ = os.path.splitext(os.path.basename(gb_file)) 36 | tar.add(gb_file, arcname=arcname) 37 | 38 | 39 | if __name__ == "__main__": 40 | setuptools.setup( 41 | ext_package="moclo.registry", 42 | ext_modules=[Registry("cidar")], 43 | cmdclass={"build_ext": build_ext}, 44 | ) 45 | -------------------------------------------------------------------------------- /moclo-plant/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import bz2 5 | import json 6 | import glob 7 | import os 8 | import setuptools 9 | import sys 10 | import tarfile 11 | 12 | from setuptools.command.build_ext import build_ext as _build_ext 13 | 14 | 15 | class Registry(setuptools.Extension): 16 | def __init__(self, name): 17 | directory = os.path.join("registry", name) 18 | sources = glob.glob(os.path.join(directory, "*.gb")) 19 | setuptools.Extension.__init__(self, name, sources) 20 | 21 | 22 | class build_ext(_build_ext): 23 | def get_ext_filename(self, ext_name): 24 | return os.path.join(*ext_name.split(".")) + ".tar.gz" 25 | 26 | def build_extension(self, ext): 27 | # find directories 28 | registry = [] 29 | gb_dir = os.path.dirname(ext.sources[0]) 30 | dst_file = self.get_ext_fullpath(ext.name) 31 | dst_dir = os.path.dirname(dst_file) 32 | # copy sequences 33 | self.mkpath(dst_dir) 34 | with tarfile.open(dst_file, mode="w:gz") as tar: 35 | for gb_file in sorted(ext.sources): 36 | arcname, _ = os.path.splitext(os.path.basename(gb_file)) 37 | tar.add(gb_file, arcname=arcname) 38 | 39 | if __name__ == "__main__": 40 | setuptools.setup( 41 | ext_package="moclo.registry", 42 | ext_modules=[Registry("plant")], 43 | cmdclass={"build_ext": build_ext} 44 | ) 45 | -------------------------------------------------------------------------------- /moclo-ecoflex/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import bz2 5 | import json 6 | import glob 7 | import os 8 | import setuptools 9 | import sys 10 | import tarfile 11 | 12 | from setuptools.command.build_ext import build_ext as _build_ext 13 | 14 | 15 | class Registry(setuptools.Extension): 16 | def __init__(self, name): 17 | directory = os.path.join("registry", name) 18 | sources = glob.glob(os.path.join(directory, "*.gb")) 19 | setuptools.Extension.__init__(self, name, sources) 20 | 21 | 22 | class build_ext(_build_ext): 23 | def get_ext_filename(self, ext_name): 24 | return os.path.join(*ext_name.split(".")) + ".tar.gz" 25 | 26 | def build_extension(self, ext): 27 | # find directories 28 | registry = [] 29 | gb_dir = os.path.dirname(ext.sources[0]) 30 | dst_file = self.get_ext_fullpath(ext.name) 31 | dst_dir = os.path.dirname(dst_file) 32 | # copy sequences 33 | self.mkpath(dst_dir) 34 | with tarfile.open(dst_file, mode="w:gz") as tar: 35 | for gb_file in sorted(ext.sources): 36 | arcname, _ = os.path.splitext(os.path.basename(gb_file)) 37 | tar.add(gb_file, arcname=arcname) 38 | 39 | if __name__ == "__main__": 40 | setuptools.setup( 41 | ext_package="moclo.registry", 42 | ext_modules=[Registry("ecoflex")], 43 | cmdclass={"build_ext": build_ext}, 44 | ) 45 | -------------------------------------------------------------------------------- /docs/source/kits/cidar/index.rst: -------------------------------------------------------------------------------- 1 | CIDAR Kit 2 | ========= 3 | 4 | .. currentmodule:: moclo.kits.cidar 5 | 6 | .. automodule:: moclo.kits.cidar 7 | 8 | Level -1 9 | -------- 10 | 11 | Module 12 | ^^^^^^ 13 | .. autoclass:: CIDARProduct(Product) 14 | :members: 15 | :inherited-members: 16 | :special-members: __init__ 17 | 18 | 19 | Vector 20 | ^^^^^^ 21 | .. autoclass:: CIDAREntryVector(EntryVector) 22 | :members: 23 | :inherited-members: 24 | :special-members: __init__ 25 | 26 | 27 | Level 0 28 | ------- 29 | 30 | Module 31 | ^^^^^^ 32 | .. autoclass:: CIDAREntry(Entry) 33 | :members: 34 | :inherited-members: 35 | :special-members: __init__ 36 | 37 | 38 | Vector 39 | ^^^^^^ 40 | .. autoclass:: CIDARCassetteVector(CassetteVector) 41 | :members: 42 | :inherited-members: 43 | :special-members: __init__ 44 | 45 | 46 | Parts 47 | ^^^^^ 48 | 49 | .. autoclass:: CIDARPromoter(CIDARPart, CIDAREntry) 50 | .. autoclass:: CIDARRibosomeBindingSite(CIDARPart, CIDAREntry) 51 | .. autoclass:: CIDARCodingSequence(CIDARPart, CIDAREntry) 52 | .. autoclass:: CIDARTerminator(CIDARPart, CIDAREntry) 53 | 54 | 55 | 56 | Level 1 57 | ------- 58 | 59 | Module 60 | ^^^^^^ 61 | .. autoclass:: CIDARCassette(Cassette) 62 | :members: 63 | 64 | Vector 65 | ^^^^^^ 66 | .. autoclass:: CIDARDeviceVector(DeviceVector) 67 | :members: 68 | 69 | 70 | Level 2 71 | ------- 72 | 73 | Module 74 | ^^^^^^ 75 | 76 | .. autoclass:: CIDARDevice(Device) 77 | :members: 78 | -------------------------------------------------------------------------------- /moclo-ytk/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import bz2 5 | import json 6 | import glob 7 | import os 8 | import setuptools 9 | import sys 10 | import tarfile 11 | 12 | from setuptools.command.build_ext import build_ext as _build_ext 13 | 14 | 15 | class Registry(setuptools.Extension): 16 | def __init__(self, name): 17 | directory = os.path.join("registry", name) 18 | sources = glob.glob(os.path.join(directory, "*.gb")) 19 | setuptools.Extension.__init__(self, name, sources) 20 | 21 | 22 | class build_ext(_build_ext): 23 | def get_ext_filename(self, ext_name): 24 | return os.path.join(*ext_name.split(".")) + ".tar.gz" 25 | 26 | def build_extension(self, ext): 27 | # find directories 28 | registry = [] 29 | gb_dir = os.path.dirname(ext.sources[0]) 30 | dst_file = self.get_ext_fullpath(ext.name) 31 | dst_dir = os.path.dirname(dst_file) 32 | # copy sequences 33 | self.mkpath(dst_dir) 34 | with tarfile.open(dst_file, mode="w:gz") as tar: 35 | for gb_file in sorted(ext.sources): 36 | arcname, _ = os.path.splitext(os.path.basename(gb_file)) 37 | tar.add(gb_file, arcname=arcname) 38 | 39 | 40 | if __name__ == "__main__": 41 | setuptools.setup( 42 | ext_package="moclo.registry", 43 | ext_modules=[Registry("ytk"), Registry("ptk")], 44 | cmdclass={"build_ext": build_ext}, 45 | ) 46 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | - "ecoflex/*.*.*" 8 | - "ytk/*.*.*" 9 | - "cidar/*.*.*" 10 | - "ig/*.*.*" 11 | - "gb3/*.*.*" 12 | 13 | jobs: 14 | 15 | package: 16 | runs-on: ubuntu-latest 17 | name: Build package distributions 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | with: 22 | submodules: true 23 | - name: Setup Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: 3.9 27 | - name: Update pip 28 | run: python -m pip install -U pip wheel setuptools docutils 29 | - name: Build wheels for the right package 30 | run: python .github/workflows/before-deploy.py 31 | - name: Store built distributions 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: dist 35 | path: dist/* 36 | 37 | upload: 38 | environment: PyPI 39 | runs-on: ubuntu-latest 40 | name: Upload 41 | needs: 42 | - package 43 | steps: 44 | - name: Download built distributions 45 | uses: actions/download-artifact@v2 46 | with: 47 | name: dist 48 | path: dist 49 | - name: Publish distributions to PyPI 50 | if: startsWith(github.ref, 'refs/tags/v') 51 | uses: pypa/gh-action-pypi-publish@master 52 | with: 53 | user: __token__ 54 | password: ${{ secrets.PYPI_API_TOKEN }} 55 | skip_existing: false 56 | -------------------------------------------------------------------------------- /docs/source/concepts/definitions.rst: -------------------------------------------------------------------------------- 1 | Definitions 2 | =========== 3 | 4 | 5 | Molecular Cloning 6 | Molecular cloning is the process of assembling together fragments of DNA to 7 | obtain a more complex molecule, often presenting genetic features of interest. 8 | It describes a *process*, not a *technique* 9 | 10 | 11 | GoldenGate 12 | GoldenGate is a molecular cloning technique that uses Type IIS restriction 13 | enzymes to cut and assemble DNA sequences into recombinant DNA molecules. 14 | It describes a *technique* 15 | 16 | 17 | Modular Cloning 18 | A Modular Cloning system uses the GoldenGate technique to assemble several 19 | genetic *modules* of a given level into a *vector* of the same level. It can 20 | also define *types*, which are modules or vectors with specific overhangs 21 | that are collections of sequences that are functionnally and structuraly 22 | equivalent to each other. 23 | 24 | 25 | MoClo 26 | MoClo is originally the name of a modular cloning system published by the 27 | Marillonnet Lab which defines a set of vectors and modules to be used to 28 | assemble multigenic expression devices for plants. An extension was later 29 | provided by the same team proposing potentially infinite assemblies multigenic 30 | expression devices with the addition of two levels. 31 | Other modular cloning systems, inspired by them, were published under the 32 | name of MoClo (such as MoClo YTK, MoClo CIDAR, MoClo EcloFlex, etc.). In 33 | this work, the original toolkit is named MoClo IG, and MoClo is used as an 34 | abbreviation of modular cloning as defined above. 35 | -------------------------------------------------------------------------------- /moclo/moclo/_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import functools 6 | import inspect 7 | import warnings 8 | 9 | 10 | class classproperty(object): 11 | """A class `property` decorator. 12 | """ 13 | 14 | def __init__(self, getter): 15 | self.getter = getter 16 | 17 | def __get__(self, instance, owner): 18 | return self.getter(owner) 19 | 20 | 21 | def isabstract(cls): 22 | return inspect.isabstract(cls) or any( 23 | getattr(cls, attr, None) is NotImplemented for attr in dir(cls) 24 | ) 25 | 26 | 27 | def catch_warnings(action, category=Warning, lineno=0, append=False): 28 | """Wrap the function in a `warnings.catch_warnings` context. 29 | 30 | It can be used to silence some particular specific warnings, or instead 31 | to treat them as errors within the function body. 32 | 33 | Example: 34 | >>> import warnings 35 | >>> from moclo.utils import catch_warnings 36 | >>> @catch_warnings('ignore') 37 | ... def are_you_scared(): 38 | ... warnings.warn("I'm warning you !") 39 | ... return False 40 | >>> are_you_scared() 41 | False 42 | 43 | """ 44 | 45 | def decorator(func): 46 | @functools.wraps(func) 47 | def newfunc(*args, **kwargs): 48 | with warnings.catch_warnings(): 49 | warnings.simplefilter(action, category, lineno, append) 50 | return func(*args, **kwargs) 51 | 52 | return newfunc 53 | 54 | return decorator 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Genbank compressed registries 10 | **/registry/**/*.json.bz2 11 | **/registry/**/*.tar.gz 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | # Codacy token 95 | .codacy.token 96 | 97 | # Libreoffice lock 98 | .~lock.*# 99 | -------------------------------------------------------------------------------- /docs/source/kits/ecoflex/index.rst: -------------------------------------------------------------------------------- 1 | EcoFlex Kit 2 | =========== 3 | 4 | .. currentmodule:: moclo.kits.ecoflex 5 | 6 | .. automodule:: moclo.kits.ecoflex 7 | 8 | .. Level -1 9 | .. -------- 10 | 11 | .. Module 12 | .. ^^^^^^ 13 | .. .. autoclass:: EcoFlexProduct(Product) 14 | .. :members: 15 | .. :inherited-members: 16 | .. :special-members: __init__ 17 | 18 | 19 | .. Vector 20 | .. ^^^^^^ 21 | .. .. autoclass:: EcoFlexEntryVector(EntryVector) 22 | .. :members: 23 | .. :inherited-members: 24 | .. :special-members: __init__ 25 | 26 | 27 | Level 0 28 | ------- 29 | 30 | Module 31 | ^^^^^^ 32 | .. autoclass:: EcoFlexEntry(Entry) 33 | :members: 34 | :inherited-members: 35 | :special-members: __init__ 36 | 37 | 38 | Vector 39 | ^^^^^^ 40 | .. autoclass:: EcoFlexCassetteVector(CassetteVector) 41 | :members: 42 | :inherited-members: 43 | :special-members: __init__ 44 | 45 | 46 | Parts 47 | ^^^^^ 48 | 49 | .. autoclass:: EcoFlexPromoter(EcoFlexPart, EcoFlexEntry) 50 | .. autoclass:: EcoFlexRBS(EcoFlexPart, EcoFlexEntry) 51 | .. autoclass:: EcoFlexTagLinker(EcoFlexPart, EcoFlexEntry) 52 | .. autoclass:: EcoFlexTag(EcoFlexPart, EcoFlexEntry) 53 | .. autoclass:: EcoFlexCodingSequence(EcoFlexPart, EcoFlexEntry) 54 | .. autoclass:: EcoFlexTerminator(EcoFlexPart, EcoFlexEntry) 55 | 56 | 57 | 58 | Level 1 59 | ------- 60 | 61 | Module 62 | ^^^^^^ 63 | .. autoclass:: EcoFlexCassette(Cassette) 64 | :members: 65 | 66 | Vector 67 | ^^^^^^ 68 | .. autoclass:: EcoFlexDeviceVector(DeviceVector) 69 | :members: 70 | 71 | 72 | Level 2 73 | ------- 74 | 75 | Module 76 | ^^^^^^ 77 | 78 | .. autoclass:: EcoFlexDevice(Device) 79 | :members: 80 | -------------------------------------------------------------------------------- /tests/test_ecoflex.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from moclo.kits import ecoflex 6 | from moclo.registry.ecoflex import EcoFlexRegistry 7 | 8 | from ._utils import PartsMetaCase 9 | 10 | 11 | # --- Test Suite Metaclass --------------------------------------------------- 12 | 13 | _Meta = PartsMetaCase("EcoFlex", EcoFlexRegistry, __name__) 14 | 15 | 16 | def exclude_tu2(item): 17 | return item.id.startswith("pTU2") 18 | 19 | 20 | def exclude_tu2a(item): 21 | return item.id.startswith(("pTU2-A", "pTU2-a")) 22 | 23 | 24 | def exclude_tu2d(item): 25 | return item.id.startswith("pTU2-D") 26 | 27 | 28 | def exclude_tu3(item): 29 | return item.id.startswith("pTU3") 30 | 31 | 32 | def exclude_cassette(item): 33 | return item.id.startswith(("pTU1", "pTU3")) 34 | 35 | # --- Test EcoFlex Parts ----------------------------------------------------- 36 | 37 | 38 | TestEcoFlexPromoter = _Meta(ecoflex.EcoFlexPromoter, "Promoter", exclude_tu2a) 39 | TestEcoFlexRBS = _Meta(ecoflex.EcoFlexRBS, "RBS") 40 | TestEcoFlexTag = _Meta(ecoflex.EcoFlexTag, "Tag") 41 | TestEcoFlexTerminator = _Meta(ecoflex.EcoFlexTerminator, "Terminator", exclude_tu2d) 42 | TestEcoFlexCodingSequence = _Meta(ecoflex.EcoFlexCodingSequence, "CDS") 43 | TestEcoFlexPromoterRBS = _Meta(ecoflex.EcoFlexPromoterRBS, "PromoterRBS") 44 | TestEcoFlexTagLinker = _Meta(ecoflex.EcoFlexTagLinker, "TagLinker") 45 | 46 | # --- Test EcoFlex Vectors --------------------------------------------------- 47 | 48 | TestEcoFlexCassetteVector = _Meta(ecoflex.EcoFlexCassetteVector, "CassetteVector", exclude_tu2) 49 | TestEcoFlexDeviceVector = _Meta(ecoflex.EcoFlexDeviceVector, "DeviceVector", exclude_cassette) 50 | -------------------------------------------------------------------------------- /moclo-ecoflex/moclo/registry/ecoflex.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """EcoFlex ToolKit sequences registry. 3 | 4 | Sequences were obtained from two sources: 5 | * The EcoFlex plasmid files distributed with the kit in a zip archive, 6 | available under the *Protocol & Resources* tab of the EcoFlex repository. 7 | * The individual plasmid files from their dedicated AddGene webpages, using 8 | the *full depositor* sequences. 9 | 10 | In case of mismatch between the two sources, the Zip sequences were used 11 | preferably. 12 | 13 | Plasmids were rotated to share the same origin, using the start of the 14 | *BioBrick* prefix as a reference location. This ensures no feature overlaps 15 | the zero coordinate, to ensure a complete Biopython compatibility. 16 | 17 | Common features were colored using the same palette as in the *Yeast ToolKit*. 18 | *AmpR* and *CmR* received additional cross-references from Swiss-Prot, as well 19 | as *GFP* and *mRFP1*. 20 | 21 | See Also: 22 | The annotation script running on Python 3 in the `GitHub repository 23 | _`. 24 | 25 | """ 26 | from __future__ import absolute_import 27 | from __future__ import unicode_literals 28 | 29 | import six 30 | 31 | from ..kits import ecoflex 32 | from .base import EmbeddedRegistry 33 | from ._utils import find_resistance 34 | 35 | 36 | class EcoFlexRegistry(EmbeddedRegistry): 37 | 38 | _module = __name__ 39 | _file = "ecoflex.tar.gz" 40 | 41 | _VECTORS = { 42 | "pTU1": ecoflex.EcoFlexCassetteVector, 43 | "pTU2": ecoflex.EcoFlexDeviceVector, 44 | "pTU3": ecoflex.EcoFlexCassetteVector, 45 | } 46 | 47 | def _load_entity(self, record): 48 | for prefix, cls in six.iteritems(self._VECTORS): 49 | if record.id.startswith(prefix): 50 | return cls(record) 51 | return ecoflex.EcoFlexPart.characterize(record) 52 | -------------------------------------------------------------------------------- /moclo-ytk/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | Unreleased_ 10 | ----------- 11 | 12 | Changed 13 | ''''''' 14 | - Update Pichia ToolKit sequences to latest AddGene data update (``1.6.2``). 15 | 16 | Added 17 | ''''' 18 | - This *CHANGELOG* file. 19 | 20 | 21 | v0.4.0_ - 2018-08-16 22 | -------------------- 23 | 24 | Changed 25 | ''''''' 26 | - Bumped ``moclo`` minimal required version to ``v0.4.0``. 27 | 28 | Documented 29 | '''''''''' 30 | - Fixed class hierarchy in API documentation. 31 | 32 | 33 | v0.3.0_ - 2018-08-07 34 | -------------------- 35 | 36 | Changed 37 | ''''''' 38 | - Bumped ``moclo`` minimal required version to ``v0.3.0``. 39 | 40 | Documented 41 | '''''''''' 42 | - Fix links to documentation in ``README.rst``. 43 | - Add YTK specific notebook in a Docker image. 44 | 45 | 46 | v0.2.0_ - 2018-07-24 47 | -------------------- 48 | 49 | Added 50 | ''''' 51 | - Reference Yeast ToolKit sequences in ``moclo.registry.ytk.YTKRegistry``. 52 | - Reference Pichia ToolKit sequences in ``moclo.registry.ytk.PTKRegistry``. 53 | 54 | Changed 55 | ''''''' 56 | - Redefined ``YTKProduct._structure`` as a public static method. 57 | 58 | 59 | v0.1.0_ - 2018-07-12 60 | -------------------- 61 | 62 | Initial public release. 63 | 64 | 65 | .. _Unreleased: https://github.com/althonos/moclo/compare/ytk/v0.4.0...HEAD 66 | .. _v0.4.0: https://github.com/althonos/moclo/compare/ytk/v0.3.0...ytk/v0.4.0 67 | .. _v0.3.0: https://github.com/althonos/moclo/compare/ytk/v0.2.0...ytk/v0.3.0 68 | .. _v0.2.0: https://github.com/althonos/moclo/compare/ytk/v0.1.0...ytk/v0.2.0 69 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...ytk/v0.1.0 70 | -------------------------------------------------------------------------------- /moclo-moclo/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import bz2 5 | import json 6 | import glob 7 | import os 8 | import setuptools 9 | import sys 10 | 11 | from setuptools.command.build_ext import build_ext as _build_ext 12 | 13 | 14 | if sys.version_info[0] == 2: 15 | 16 | def bz2_open_write(filename): 17 | return bz2.BZ2File(filename, "w") 18 | 19 | 20 | else: 21 | 22 | def bz2_open_write(filename): 23 | return bz2.open(filename, "wt") 24 | 25 | 26 | class Registry(setuptools.Extension): 27 | def __init__(self, name): 28 | directory = os.path.join("registry", name) 29 | sources = glob.glob(os.path.join(directory, "*.gb")) 30 | setuptools.Extension.__init__(self, name, sources) 31 | 32 | 33 | class build_ext(_build_ext): 34 | def get_ext_filename(self, ext_name): 35 | return os.path.join(*ext_name.split(".")) + ".json.bz2" 36 | 37 | def build_extension(self, ext): 38 | # find directories 39 | registry = [] 40 | gb_dir = os.path.dirname(ext.sources[0]) 41 | dst_file = self.get_ext_fullpath(ext.name) 42 | dst_dir = os.path.dirname(dst_file) 43 | # read all records 44 | self.announce("collecting records from {}".format(gb_dir), 2) 45 | for gb_file in sorted(ext.sources): 46 | id_, _ = os.path.splitext(os.path.basename(gb_file)) 47 | with open(gb_file) as gb_rec: 48 | registry.append({"id": id_, "gb": gb_rec.read()}) 49 | # write the compressed registry 50 | self.mkpath(dst_dir) 51 | self.announce("writing {} records to {}".format(len(registry), dst_file), 2) 52 | with bz2_open_write(dst_file) as reg: 53 | json.dump(registry, reg) 54 | 55 | 56 | if __name__ == "__main__": 57 | setuptools.setup( 58 | ext_package="moclo.registry", 59 | ext_modules=[], 60 | cmdclass={"build_ext": build_ext} 61 | ) 62 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | Library Reference 2 | ================= 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | record 8 | registry 9 | modules 10 | vectors 11 | parts 12 | errors 13 | 14 | 15 | Record (`moclo.record`) 16 | ----------------------- 17 | 18 | .. currentmodule:: moclo.record 19 | .. autosummary:: 20 | :nosignatures: 21 | 22 | CircularRecord 23 | 24 | 25 | Registry (`moclo.registry.base`) 26 | -------------------------------- 27 | 28 | .. currentmodule:: moclo.registry.base 29 | .. autosummary:: 30 | :nosignatures: 31 | 32 | Item 33 | AbstractRegistry 34 | CombinedRegistry 35 | EmbeddedRegistry 36 | 37 | 38 | .. currentmodule:: moclo.core 39 | 40 | 41 | Modules (`moclo.core.modules`) 42 | ------------------------------ 43 | 44 | .. currentmodule:: moclo.core.modules 45 | .. autosummary:: 46 | :nosignatures: 47 | 48 | AbstractModule 49 | Entry 50 | Cassette 51 | Device 52 | 53 | 54 | Vectors (`moclo.core.vectors`) 55 | ------------------------------ 56 | 57 | .. currentmodule:: moclo.core.vectors 58 | .. autosummary:: 59 | :nosignatures: 60 | 61 | AbstractVector 62 | EntryVector 63 | CassetteVector 64 | DeviceVector 65 | 66 | Parts (`moclo.core.parts`) 67 | -------------------------- 68 | 69 | .. currentmodule:: moclo.core.parts 70 | .. autosummary:: 71 | :nosignatures: 72 | 73 | AbstractPart 74 | 75 | 76 | Errors (`moclo.errors`) 77 | ----------------------- 78 | 79 | .. currentmodule:: moclo.errors 80 | .. rubric:: Base classes 81 | .. autosummary:: 82 | :nosignatures: 83 | 84 | MocloError 85 | AssemblyError 86 | AssemblyWarning 87 | 88 | .. rubric:: Errors 89 | .. autosummary:: 90 | :nosignatures: 91 | 92 | DuplicateModules 93 | InvalidSequence 94 | IllegalSite 95 | MissingModule 96 | 97 | .. rubric:: Warnings 98 | .. autosummary:: 99 | :nosignatures: 100 | 101 | UnusedModules 102 | -------------------------------------------------------------------------------- /docs/source/_scripts/ytk_parts.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import gzip 6 | import os 7 | 8 | import Bio.Seq 9 | 10 | # Path to doc files 11 | DOCSRC_DIR = os.path.abspath(os.path.join(__file__, '..', '..')) 12 | YTKKIT_DIR = os.path.join(DOCSRC_DIR, 'kits', 'ytk') 13 | 14 | # Image configuration for each part 15 | PARTS_CONF = { 16 | 'type1': ('CCCT', '84c4dd', 'TTGC', 'Type 1'), 17 | 'type2': ('AACG', 'a9d55e', 'ATAC', 'Type 2'), 18 | 'type3': ('TATG', 'f3eb91', 'TAGG', 'Type 3'), 19 | 'type3a': ('TATG', 'f3eb91', 'AAGA', 'Type 3a'), 20 | 'type3b': ('TTCT', 'f3eb91', 'TAGG', 'Type 3b'), 21 | 'type4': ('ATCC', 'ee8d9c', 'CGAC', 'Type 4'), 22 | 'type4a': ('ATCC', 'ee8d9c', 'ACCG', 'Type 4a'), 23 | 'type4b': ('TGGC', 'ee8d9c', 'CGAC', 'Type 4b'), 24 | 'type5': ('GCTG', '8c69d5', 'ATGT', 'Type 5'), 25 | 'type6': ('TACA', 'f4d091', 'CTCA', 'Type 6'), 26 | 'type7': ('GAGT', '8c6239', 'GGCT', 'Type 7'), 27 | 'type8': ('CCGA', '7f7f7f', 'GGGA', 'Type 8'), 28 | 'type8a': ('CCGA', '7f7f7f', 'GTTA', 'Type 8a'), 29 | 'type8b': ('CAAT', '7f7f7f', 'GGGA', 'Type 8b'), 30 | } 31 | 32 | 33 | def generate_svg(workdir=YTKKIT_DIR, 34 | template=os.path.join(YTKKIT_DIR, 'part.svg.gz'), 35 | conf=PARTS_CONF): 36 | 37 | with gzip.open(template, 'rt') as handle: 38 | template = handle.read() 39 | 40 | for id_, (up, color, down_r, name) in conf.items(): 41 | filename = os.path.join(YTKKIT_DIR, '{}.svg'.format(id_)) 42 | up_seq = Bio.Seq.Seq(up) 43 | down_r_seq = Bio.Seq.Seq(down_r) 44 | 45 | with open(filename, 'w') as handle: 46 | handle.write(template.format( 47 | upstream=up_seq, 48 | upstream_r=up_seq.complement(), 49 | downstream=down_r_seq.complement(), 50 | downstream_r=down_r, 51 | type=name, 52 | color=color 53 | )) 54 | -------------------------------------------------------------------------------- /docs/source/kits/ytk/index.rst: -------------------------------------------------------------------------------- 1 | Yeast ToolKit (YTK) / Pichia ToolKit (PTK) 2 | ========================================== 3 | 4 | .. currentmodule:: moclo.kits.ytk 5 | 6 | .. automodule:: moclo.kits.ytk 7 | 8 | Level -1 9 | -------- 10 | 11 | Module 12 | ^^^^^^ 13 | .. autoclass:: YTKProduct(Product) 14 | :members: 15 | :inherited-members: 16 | :special-members: __init__ 17 | 18 | 19 | Vector 20 | ^^^^^^ 21 | .. autoclass:: YTKEntryVector(EntryVector) 22 | :members: 23 | :inherited-members: 24 | :special-members: __init__ 25 | 26 | 27 | Level 0 28 | ------- 29 | 30 | Module 31 | ^^^^^^ 32 | .. autoclass:: YTKEntry(Entry) 33 | :members: 34 | :inherited-members: 35 | :special-members: __init__ 36 | 37 | 38 | Vector 39 | ^^^^^^ 40 | .. autoclass:: YTKCassetteVector(CassetteVector) 41 | :members: 42 | :inherited-members: 43 | :special-members: __init__ 44 | 45 | 46 | Parts 47 | ^^^^^ 48 | 49 | Base Parts 50 | '''''''''' 51 | 52 | .. autoclass:: YTKPart1(YTKPart, YTKEntry) 53 | .. autoclass:: YTKPart2(YTKPart, YTKEntry) 54 | .. autoclass:: YTKPart3(YTKPart, YTKEntry) 55 | .. autoclass:: YTKPart3a(YTKPart, YTKEntry) 56 | .. autoclass:: YTKPart3b(YTKPart, YTKEntry) 57 | .. autoclass:: YTKPart4(YTKPart, YTKEntry) 58 | .. autoclass:: YTKPart4a(YTKPart, YTKEntry) 59 | .. autoclass:: YTKPart4b(YTKPart, YTKEntry) 60 | .. autoclass:: YTKPart5(YTKPart, YTKEntry) 61 | .. autoclass:: YTKPart6(YTKPart, YTKEntry) 62 | .. autoclass:: YTKPart7(YTKPart, YTKEntry) 63 | .. autoclass:: YTKPart8(YTKPart, YTKCassetteVector) 64 | .. autoclass:: YTKPart8a(YTKPart, YTKCassetteVector) 65 | .. autoclass:: YTKPart8b(YTKPart, YTKEntry) 66 | 67 | Composite 68 | ''''''''' 69 | 70 | .. autoclass:: YTKPart234(YTKPart, YTKEntry) 71 | .. autoclass:: YTKPart234r(YTKPart, YTKEntry) 72 | .. autoclass:: YTKPart678(YTKPart, YTKCassetteVector) 73 | 74 | 75 | Level 1 76 | ------- 77 | 78 | Module 79 | ^^^^^^ 80 | .. autoclass:: YTKCassette(Cassette) 81 | :members: 82 | 83 | Vector 84 | ^^^^^^ 85 | .. autoclass:: YTKDeviceVector(DeviceVector) 86 | :members: 87 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The ``moclo`` module is designed to be modular, and as such, you only need to 5 | install whatever functionalities you are willing to use. Packages are distributed 6 | on PyPI, and it is advised to use ``pip`` to install them. See the 7 | `pip documentation `_ to get pip if 8 | it is not installed on your system. 9 | 10 | Commands below use ``pip`` in user mode: the packages will be installed in a 11 | user-dependent location, and no additional permissions are needed. If for some 12 | reason you need a system-wide setup, remove the ``--user`` flag. Installing in 13 | user-mode should be prefered to avoid dependency issues, in particular when on 14 | an OS which provides a package manager (such as ``aptitude`` on Debian, or even 15 | ``homebrew`` on Mac OSX). 16 | 17 | 18 | PyPI + ``pip`` |PyPI| 19 | --------------------- 20 | 21 | .. |PyPI| image:: https://img.shields.io/pypi/v/moclo.svg?style=flat-square&maxAge=3600 22 | :target: https://pypi.python.org/pypi/moclo 23 | 24 | To download the latest releases from the Python Package Index: 25 | 26 | .. code-block:: console 27 | 28 | $ pip install --user moclo moclo-ytk moclo-cidar moclo-ecoflex 29 | 30 | 31 | GitHub + ``pip`` |Travis| 32 | ------------------------- 33 | 34 | .. |Travis| image:: https://img.shields.io/travis/althonos/moclo.svg?style=flat-square&maxAge=3600 35 | :target: https://travis-ci.org/althonos/moclo 36 | 37 | To download the development version from the source repository, you can specify 38 | a subfolder in the installation command and directly install it: 39 | 40 | .. code-block:: console 41 | 42 | $ pip install --user git+https://github.com/althonos/moclo#subdirectory=moclo 43 | $ pip install --user git+https://github.com/althonos/moclo#subdirectory=moclo-ytk 44 | $ pip install --user git+https://github.com/althonos/moclo#subdirectory=moclo-cidar 45 | $ pip install --user git+https://github.com/althonos/moclo#subdirectory=moclo-ecoflex 46 | 47 | Check the CI build is passing, or else you may be installing a broken version of 48 | the library ! 49 | -------------------------------------------------------------------------------- /moclo/moclo/core/_structured.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import abc 6 | import typing 7 | 8 | import six 9 | from property_cached import cached_property 10 | 11 | from .. import errors 12 | from ..regex import DNARegex 13 | 14 | if typing.TYPE_CHECKING: 15 | from typing import Text, Type # noqa: F401 16 | from Bio.SeqRecord import SeqRecord # noqa: F401 17 | from ..regex import SeqMatch # noqa: F401 18 | 19 | 20 | @six.add_metaclass(abc.ABCMeta) 21 | class StructuredRecord(object): 22 | """A DNA record with a specific structure. 23 | """ 24 | 25 | _regex = None 26 | 27 | def __init__(self, record): 28 | # type: (SeqRecord) -> None 29 | self.record = record 30 | self.seq = record.seq 31 | 32 | @classmethod 33 | @abc.abstractmethod 34 | def structure(cls): 35 | # type: () -> Text 36 | """Get the expected record structure as a DNA pattern in regex syntax. 37 | """ 38 | return NotImplemented 39 | 40 | @classmethod 41 | def _get_regex(cls): 42 | if cls._regex is None: 43 | cls._regex = DNARegex(cls.structure()) 44 | return cls._regex 45 | 46 | @cached_property 47 | def _match(self): 48 | # type: () -> SeqMatch[SeqRecord] 49 | topology = self.record.annotations.get("topology", "circular").lower() 50 | match = self._get_regex().search(self.record, linear=topology != "circular") 51 | if match is None: 52 | details = "does not match '{}' structure".format(type(self).__name__) 53 | raise errors.InvalidSequence(self.record, details=details) 54 | return match 55 | 56 | def is_valid(self): 57 | # type: () -> bool 58 | """Check if the wrapped record follows the required class structure. 59 | 60 | Returns: 61 | `bool`: `True` if the record is valid, `False` otherwise. 62 | 63 | """ 64 | try: 65 | return self._match is not None 66 | except errors.InvalidSequence: 67 | return False 68 | -------------------------------------------------------------------------------- /moclo-cidar/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | Unreleased_ 10 | ----------- 11 | 12 | Added 13 | ''''' 14 | - This *CHANGELOG* file. 15 | 16 | Changed 17 | ''''''' 18 | - Update CIDAR sequences to latest AddGene data update (``1.6.2``). 19 | 20 | 21 | v0.4.0_ - 2018-08-16 22 | -------------------- 23 | 24 | Changed 25 | ''''''' 26 | - Bumped ``moclo`` minimal required version to ``v0.4.0``. 27 | 28 | Documented 29 | '''''''''' 30 | - Add *SVG* images illustrating CIDAR parts to the API documentation. 31 | - Fixed class hierarchy in API documentation. 32 | 33 | 34 | v0.3.0_ - 2018-08-07 35 | -------------------- 36 | 37 | Changed 38 | ''''''' 39 | - Bumped ``moclo`` minimal required version to ``v0.3.0``. 40 | 41 | Removed 42 | ''''''' 43 | - Location attribute handler from ``CIDARRegistry``. 44 | - *DVA* and *DVK* sequences from the registry as they are not MoClo elements. 45 | 46 | 47 | v0.2.0_ - 2018-07-25 48 | -------------------- 49 | 50 | Added 51 | ''''' 52 | - Partial reference CIDAR sequences in ``moclo.registry.cidar.CIDARRegistry``. 53 | 54 | Changed 55 | ''''''' 56 | - Use signature and cutter to generate structures of ``moclo.kits.cidar.CIDARPart`` 57 | subclasses. 58 | - Bumped ``moclo`` minimal required version to ``v0.2.0``. 59 | 60 | Documented 61 | '''''''''' 62 | - Fixed link to documentation in ``README.rst``. 63 | 64 | 65 | v0.1.0_ - 2018-07-12 66 | -------------------- 67 | 68 | Initial public release. 69 | 70 | 71 | .. _Unreleased: https://github.com/althonos/moclo/compare/cidar/v0.4.0...HEAD 72 | .. _v0.4.0: https://github.com/althonos/moclo/compare/cidar/v0.3.0...cidar/v0.4.0 73 | .. _v0.3.0: https://github.com/althonos/moclo/compare/cidar/v0.2.0...cidar/v0.3.0 74 | .. _v0.2.0: https://github.com/althonos/moclo/compare/cidar/v0.1.0...cidar/v0.2.0 75 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...cidar/v0.1.0 76 | -------------------------------------------------------------------------------- /moclo-ecoflex/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | Unreleased_ 10 | ----------- 11 | 12 | Fixed 13 | ''''' 14 | - Annotations of ``CmR`` cassette in ``pBP-BBa_B0034``. 15 | - Add missing sequences to the EcoFlex registry: 16 | 17 | * Promoters: ``pBP-SJM9**`` series. 18 | 19 | 20 | v0.3.1_ - 2018-11-19 21 | -------------------- 22 | 23 | Added 24 | ''''' 25 | - This *CHANGELOG* file. 26 | 27 | Fixed 28 | ''''' 29 | - Wheel distribution not embedding the ``moclo.registry.ecoflex`` module. 30 | - Add missing sequences to the EcoFlex registry: 31 | 32 | * Promoters: ``pBP-BBa_B0012``, ``pBP-BBa_B0015``, ``pBP-BBa_B0034``, 33 | * Tags: ``pBP-HexHis`` 34 | * CDS: ``pBP-eCFP``, ``pBP-eGFP`` 35 | * Promoter + RBS: ``pBP-T7-RBS-His6`` 36 | * Device Vectors: ``pTU2-a-RFP``, ``pTU2-b-RFP`` 37 | 38 | 39 | v0.3.0_ - 2018-08-16 40 | -------------------- 41 | 42 | Changed 43 | ''''''' 44 | - Bumped ``moclo`` minimal required version to ``v0.4.0``. 45 | 46 | Documented 47 | '''''''''' 48 | - Fixed class hierarchy in API documentation. 49 | 50 | 51 | v0.2.0_ - 2018-08-07 52 | -------------------- 53 | 54 | Added 55 | ''''' 56 | - Partial reference EcoFlex sequences in ``moclo.registry.ecoflex.EcoFlexRegistry``. 57 | 58 | Changed 59 | ''''''' 60 | - Use signature and cutter to generate structures of ``moclo.kits.ecoflex.EcoFlexPart`` 61 | subclasses. 62 | - Bumped ``moclo`` minimal required version to ``v0.3.0``. 63 | 64 | 65 | v0.1.0_ - 2018-07-12 66 | -------------------- 67 | 68 | Initial public release. 69 | 70 | 71 | .. _Unreleased: https://github.com/althonos/moclo/compare/ecoflex/v0.3.1...HEAD 72 | .. _v0.3.1: https://github.com/althonos/moclo/compare/ecoflex/v0.3.0...ecoflex/v0.3.1 73 | .. _v0.3.0: https://github.com/althonos/moclo/compare/ecoflex/v0.2.0...ecoflex/v0.3.0 74 | .. _v0.2.0: https://github.com/althonos/moclo/compare/ecoflex/v0.1.0...ecoflex/v0.2.0 75 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...ecoflex/v0.1.0 76 | -------------------------------------------------------------------------------- /moclo-plant/moclo/kits/plant.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """An implementation of the Plant Parts Kit for the Python MoClo library. 3 | 4 | References: 5 | 1. `Weber, E., Engler, C., Gruetzner, R., Werner, S., Marillonnet, S. (2011). 6 | A Modular Cloning System for Standardized Assembly of Multigene Constructs. 7 | PLOS ONE, 6(2), e16765. 8 | `_ 9 | 2. `Werner, S., Engler, C., Weber, E., Gruetzner, R., & Marillonnet, S. (2012). 10 | Fast track assembly of multigene constructs using Golden Gate cloning 11 | and the MoClo system. Bioengineered, 3(1), 38–43. 12 | `_ 13 | 14 | """ 15 | from __future__ import absolute_import 16 | from __future__ import unicode_literals 17 | 18 | from Bio.Restriction import BsaI, BpiI 19 | 20 | from ..core import parts, modules, vectors 21 | from .moclo import MoCloPart, MoCloEntry 22 | 23 | __author__ = "Martin Larralde " 24 | __version__ = ( 25 | __import__("pkg_resources") 26 | .resource_string(__name__, "plant.version") 27 | .strip() 28 | .decode("ascii") 29 | ) 30 | 31 | 32 | 33 | class Plant5U(MoCloPart, MoCloEntry): 34 | """A Plant Part 5' UTR part. 35 | """ 36 | 37 | signature = ("TACT", "CCAT") 38 | 39 | 40 | class Plant5Uf(MoCloPart, MoCloEntry): 41 | """A Plant Part 5' UTR part to use with a fusion tag. 42 | """ 43 | 44 | signature = ("TACT", "AATG") 45 | 46 | 47 | class PlantPro5Uf(MoCloPart, MoCloEntry): 48 | """A Plant Part containing promoter and a 5'UTR region for fusion tag. 49 | """ 50 | 51 | signature = ("GGAG", "CCAT") 52 | 53 | 54 | class PlantPro5U(MoCloPart, MoCloEntry): 55 | """A Plant Part containing a promoter and 5'UTR region. 56 | """ 57 | 58 | signature = ("GGAG", "AATG") 59 | 60 | 61 | class PlantNSignal(MoCloPart, MoCloEntry): 62 | signature = ("CCAT", "AATG") 63 | 64 | 65 | class PlantFullCDS(MoCloPart, MoCloEntry): 66 | signature = ("AATG", "GCTT") 67 | 68 | 69 | class PlantCDS(MoCloPart, MoCloEntry): 70 | signature = ("AATG", "TTCG") 71 | 72 | 73 | class PlantCDSNonStop(MoCloPart, MoCloEntry): 74 | signature = ("AGGT", "TTCG") 75 | 76 | 77 | class PlantCSignal(MoCloPart, MoCloEntry): 78 | signature = ("TTCG", "GCTT") 79 | 80 | 81 | class Plant3U(MoCloPart, MoCloEntry): 82 | signature = ("GCTT", "GGTA") 83 | 84 | 85 | class PlantTer(MoCloPart, MoCloEntry): 86 | signature = ("GGTA", "CGCT") 87 | -------------------------------------------------------------------------------- /docs/source/kits/moclo/index.rst: -------------------------------------------------------------------------------- 1 | MoClo Kit 2 | ========= 3 | 4 | .. currentmodule:: moclo.kits.moclo 5 | 6 | .. automodule:: moclo.kits.moclo 7 | 8 | Level -1 9 | -------- 10 | 11 | Module 12 | ^^^^^^ 13 | .. autoclass:: MoCloProduct(Product) 14 | :members: 15 | :inherited-members: 16 | :special-members: __init__ 17 | 18 | 19 | Vector 20 | ^^^^^^ 21 | .. autoclass:: MoCloEntryVector(EntryVector) 22 | :members: 23 | :inherited-members: 24 | :special-members: __init__ 25 | 26 | 27 | Level 0 28 | ------- 29 | 30 | Module 31 | ^^^^^^ 32 | .. autoclass:: MoCloEntry(Entry) 33 | :members: 34 | :inherited-members: 35 | :special-members: __init__ 36 | 37 | 38 | Vector 39 | ^^^^^^ 40 | .. autoclass:: MoCloCassetteVector(CassetteVector) 41 | :members: 42 | :inherited-members: 43 | :special-members: __init__ 44 | 45 | 46 | Parts 47 | ^^^^^ 48 | 49 | .. autoclass:: MoCloPro(MoCloPart, MoCloEntry) 50 | .. autoclass:: MoClo5U(MoCloPart, MoCloEntry) 51 | .. autoclass:: MoClo5Uf(MoCloPart, MoCloEntry) 52 | .. autoclass:: MoCloNTag(MoCloPart, MoCloEntry) 53 | .. autoclass:: MoCloPro5U(MoCloPart, MoCloEntry) 54 | .. autoclass:: MoCloPro5Uf(MoCloPart, MoCloEntry) 55 | .. autoclass:: MoCloCDS1(MoCloPart, MoCloEntry) 56 | .. autoclass:: MoCloCDS1ns(MoCloPart, MoCloEntry) 57 | .. autoclass:: MoCloSP(MoCloPart, MoCloEntry) 58 | .. autoclass:: MoCloCDS2(MoCloPart, MoCloEntry) 59 | .. autoclass:: MoCloCDS2ns(MoCloPart, MoCloEntry) 60 | .. autoclass:: MoCloCTag(MoCloPart, MoCloEntry) 61 | .. autoclass:: MoClo3U(MoCloPart, MoCloEntry) 62 | .. autoclass:: MoCloTer(MoCloPart, MoCloEntry) 63 | .. autoclass:: MoClo3UTer(MoCloPart, MoCloEntry) 64 | .. autoclass:: MoCloGene(MoCloPart, MoCloEntry) 65 | 66 | 67 | Level 1 68 | ------- 69 | 70 | Module 71 | ^^^^^^ 72 | .. autoclass:: MoCloCassette(Cassette) 73 | :members: 74 | 75 | Vector 76 | ^^^^^^ 77 | .. autoclass:: MoCloDeviceVector(DeviceVector) 78 | :members: 79 | 80 | Parts 81 | ^^^^^ 82 | .. autoclass:: MoCloEndLinker(MoCloPart, MoCloCassette) 83 | :members: 84 | 85 | 86 | Level M 87 | ------- 88 | 89 | Parts 90 | ^^^^^ 91 | .. autoclass:: MoCloLevelMVector(MoCloPart, MoCloDeviceVector) 92 | :members: 93 | 94 | .. autoclass:: MoCloLevelMEndLinker(MoCloPart, MoCloCassette) 95 | :members: 96 | 97 | 98 | Level P 99 | ------- 100 | 101 | Parts 102 | ^^^^^ 103 | .. autoclass:: MoCloLevelPVector(MoCloPart, MoCloCassetteVector) 104 | :members: 105 | 106 | .. autoclass:: MoCloLevelPEndLinker(MoCloPart, MoCloEntry) 107 | :members: 108 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. moclo documentation master file, created by 2 | sphinx-quickstart on Fri Jun 22 17:45:51 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | MoClo 7 | ===== 8 | 9 | |Source| |PyPI| |Travis| |Docs| |Codecov| |Codacy| |Format| |License| 10 | 11 | .. |Codacy| image:: https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300 12 | :target: https://www.codacy.com/app/althonos/moclo 13 | 14 | .. |Codecov| image:: https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600 15 | :target: https://codecov.io/gh/althonos/moclo 16 | 17 | .. |PyPI| image:: https://img.shields.io/pypi/v/moclo.svg?style=flat-square&maxAge=300 18 | :target: https://pypi.python.org/pypi/moclo 19 | 20 | .. |Travis| image:: https://img.shields.io/travis/althonos/moclo.svg?style=flat-square&maxAge=3600 21 | :target: https://travis-ci.org/althonos/moclo/branches 22 | 23 | .. |Format| image:: https://img.shields.io/pypi/format/moclo.svg?style=flat-square&maxAge=300 24 | :target: https://pypi.python.org/pypi/moclo 25 | 26 | .. |Versions| image:: https://img.shields.io/pypi/pyversions/moclo.svg?style=flat-square&maxAge=300 27 | :target: https://travis-ci.org/althonos/moclo 28 | 29 | .. |License| image:: https://img.shields.io/pypi/l/moclo-ytk.svg?style=flat-square&maxAge=300 30 | :target: https://choosealicense.com/licenses/mit/ 31 | 32 | .. |Source| image:: https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square 33 | :target: https://github.com/althonos/moclo 34 | 35 | .. |Docs| image:: https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square 36 | :target: https://moclo.readthedocs.io/ 37 | 38 | The MoClo system is a standard for molecular cloning that relies on the Golden 39 | Gate Assembly technique. 40 | 41 | 42 | Concepts and Definitions 43 | ------------------------ 44 | 45 | .. toctree:: 46 | :maxdepth: 1 47 | 48 | concepts/index 49 | concepts/definitions 50 | theory/index 51 | 52 | 53 | Library 54 | ------- 55 | 56 | .. toctree:: 57 | :maxdepth: 1 58 | 59 | install 60 | examples/index 61 | api/index 62 | changes/index 63 | about 64 | 65 | 66 | Kits 67 | ---- 68 | 69 | .. toctree:: 70 | :maxdepth: 1 71 | 72 | kits/moclo/index 73 | kits/plant/index 74 | kits/cidar/index 75 | kits/ecoflex/index 76 | kits/ytk/index 77 | 78 | 79 | 80 | Indices and tables 81 | ------------------ 82 | 83 | * :ref:`genindex` 84 | * :ref:`modindex` 85 | * :ref:`search` 86 | -------------------------------------------------------------------------------- /moclo-plant/README.md: -------------------------------------------------------------------------------- 1 | # `moclo-plant` 2 | 3 | *Plant Parts Kit implementation for the base [`moclo`](https://github.com/althonos/moclo/tree/master/moclo>) library*. 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo-ig) 6 | [![PyPI](https://img.shields.io/pypi/v/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 7 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 8 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 9 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 10 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 11 | [![Format](https://img.shields.io/pypi/format/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 12 | [![Versions](https://img.shields.io/pypi/pyversions/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 13 | [![License](https://img.shields.io/pypi/l/moclo-ig.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 14 | 15 | 16 | ## 📚 Documentation 17 | 18 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 19 | and built against the latest commit of the development repository. It contains 20 | a comprehensive API reference as well as examples compiled from Jupyter 21 | notebooks at each build. 22 | 23 | 24 | ## 🛠️ Installation 25 | 26 | This package is available as a *wheel*, and can be installed with ``pip``: 27 | ```console 28 | $ pip install --user moclo-ig 29 | ``` 30 | 31 | To see more ways of installing, head over to the 32 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 33 | page of the online documentation. 34 | 35 | 36 | ## 📜 License 37 | 38 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 39 | 40 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 41 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 42 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 43 | at the Institut Pasteur of Paris during the summer of 2018.* 44 | -------------------------------------------------------------------------------- /moclo-moclo/README.md: -------------------------------------------------------------------------------- 1 | # `moclo-moclo` 2 | 3 | *Original MoClo (Icon Genetics) implementation for the base [`moclo`](https://github.com/althonos/moclo/tree/master/moclo>) library*. 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo-ig) 6 | [![PyPI](https://img.shields.io/pypi/v/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 7 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 8 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 9 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 10 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 11 | [![Format](https://img.shields.io/pypi/format/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 12 | [![Versions](https://img.shields.io/pypi/pyversions/moclo-ig.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ig) 13 | [![License](https://img.shields.io/pypi/l/moclo-ig.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 14 | 15 | 16 | ## 📚 Documentation 17 | 18 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 19 | and built against the latest commit of the development repository. It contains 20 | a comprehensive API reference as well as examples compiled from Jupyter 21 | notebooks at each build. 22 | 23 | 24 | ## 🛠️ Installation 25 | 26 | This package is available as a *wheel*, and can be installed with ``pip``: 27 | ```console 28 | $ pip install --user moclo-moclo 29 | ``` 30 | 31 | To see more ways of installing, head over to the 32 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 33 | page of the online documentation. 34 | 35 | 36 | ## 📜 License 37 | 38 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 39 | 40 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 41 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 42 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 43 | at the Institut Pasteur of Paris during the summer of 2018.* 44 | -------------------------------------------------------------------------------- /moclo-ytk/README.md: -------------------------------------------------------------------------------- 1 | # `moclo-ytk` 2 | 3 | *Yeast ToolKit and Pichia ToolKit implementations for the base [`moclo`](https://github.com/althonos/moclo/tree/master/moclo) library*. 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo-ytk) 6 | [![PyPI](https://img.shields.io/pypi/v/moclo-ytk.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ytk) 7 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 8 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 9 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 10 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 11 | [![Format](https://img.shields.io/pypi/format/moclo-ytk.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ytk) 12 | [![Versions](https://img.shields.io/pypi/pyversions/moclo-ytk.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ytk) 13 | [![License](https://img.shields.io/pypi/l/moclo-ytk.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 14 | 15 | ## 📚 Documentation 16 | 17 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 18 | and built against the latest commit of the development repository. It contains 19 | a comprehensive API reference as well as examples compiled from Jupyter 20 | notebooks at each build. 21 | 22 | 23 | ## 🛠️ Installation 24 | 25 | This package is available as a *wheel*, and can be installed with ``pip``: 26 | ```console 27 | $ pip install --user moclo-ytk 28 | ``` 29 | 30 | To see more ways of installing, head over to the 31 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 32 | page of the online documentation. 33 | 34 | 35 | ## 📜 License 36 | 37 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 38 | 39 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 40 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 41 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 42 | at the Institut Pasteur of Paris during the summer of 2018.* 43 | -------------------------------------------------------------------------------- /moclo-ecoflex/README.md: -------------------------------------------------------------------------------- 1 | # `moclo-ecoflex` 2 | 3 | *EcoFlex Kit implementation for the base [`moclo`](https://github.com/althonos/moclo/tree/master/moclo) library*. 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo-ecoflex) 6 | [![PyPI](https://img.shields.io/pypi/v/moclo-ecoflex.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ecoflex) 7 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 8 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 9 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 10 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 11 | [![Format](https://img.shields.io/pypi/format/moclo-ecoflex.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ecoflex) 12 | [![Versions](https://img.shields.io/pypi/pyversions/moclo-ecoflex.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-ecoflex) 13 | [![License](https://img.shields.io/pypi/l/moclo-ecoflex.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 14 | 15 | 16 | ## 📚 Documentation 17 | 18 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 19 | and built against the latest commit of the development repository. It contains 20 | a comprehensive API reference as well as examples compiled from Jupyter 21 | notebooks at each build. 22 | 23 | 24 | ## 🛠️ Installation 25 | 26 | This package is available as a *wheel*, and can be installed with ``pip``: 27 | ```console 28 | $ pip install --user moclo-ecoflex 29 | ``` 30 | 31 | To see more ways of installing, head over to the 32 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 33 | page of the online documentation. 34 | 35 | 36 | ## 📜 License 37 | 38 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 39 | 40 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 41 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 42 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 43 | at the Institut Pasteur of Paris during the summer of 2018.* 44 | -------------------------------------------------------------------------------- /tests/test_doctest.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Test doctest contained tests in every file of the module. 3 | """ 4 | 5 | import doctest 6 | import sys 7 | import re 8 | import types 9 | import unittest 10 | import warnings 11 | 12 | import Bio.Seq 13 | import Bio.SeqRecord 14 | import Bio.Restriction 15 | 16 | import moclo 17 | import moclo.core 18 | import moclo.kits 19 | 20 | 21 | class IgnoreUnicodeChecker(doctest.OutputChecker): 22 | def check_output(self, want, got, optionflags): 23 | if sys.version_info[0] > 2: 24 | want = re.sub("u'(.*?)'", "'\\1'", want) 25 | want = re.sub('u"(.*?)"', '"\\1"', want) 26 | return doctest.OutputChecker.check_output(self, want, got, optionflags) 27 | 28 | 29 | def _load_tests_from_module(tests, module, globs, setUp, tearDown): 30 | """Load tests from module, iterating through submodules. 31 | """ 32 | for attr in (getattr(module, x) for x in dir(module) if not x.startswith("_")): 33 | if isinstance(attr, types.ModuleType): 34 | tests.addTests( 35 | doctest.DocTestSuite( 36 | attr, 37 | globs=globs, 38 | setUp=setUp, 39 | tearDown=tearDown, 40 | checker=IgnoreUnicodeChecker(), 41 | ) 42 | ) 43 | return tests 44 | 45 | 46 | def load_tests(loader=None, tests=unittest.TestSuite(), ignore=False): 47 | """load_test function used by unittest to find the doctests""" 48 | 49 | def _setUp(self): 50 | return 51 | 52 | def _tearDown(self): 53 | return 54 | 55 | globs = { 56 | # Biopython 57 | "Seq": Bio.Seq.Seq, 58 | "SeqRecord": Bio.SeqRecord.SeqRecord, 59 | } 60 | 61 | # Load all abstract base classes in the namespace 62 | for name in moclo.core.__all__: 63 | globs[name] = getattr(moclo.core, name) 64 | 65 | # Load all restriction enzymes in the namespace 66 | RestrictionType = Bio.Restriction.Restriction.RestrictionType 67 | for name in dir(Bio.Restriction): 68 | attr = getattr(Bio.Restriction, name) 69 | if isinstance(attr, type) and issubclass(attr, RestrictionType): 70 | globs[name] = attr 71 | 72 | if not sys.argv[0].endswith("green"): 73 | _load_tests_from_module(tests, moclo, globs, _setUp, _tearDown) 74 | _load_tests_from_module(tests, moclo.core, globs, _setUp, _tearDown) 75 | _load_tests_from_module(tests, moclo.kits, globs, _setUp, _tearDown) 76 | 77 | return tests 78 | 79 | 80 | def setUpModule(): 81 | warnings.simplefilter("ignore") 82 | 83 | 84 | def tearDownModule(): 85 | warnings.simplefilter(warnings.defaultaction) 86 | -------------------------------------------------------------------------------- /moclo-cidar/README.md: -------------------------------------------------------------------------------- 1 | # `moclo-cidar` 2 | 3 | *CIDAR Kit implementation for the base [`moclo`](https://github.com/althonos/moclo/tree/master/moclo) library*. 4 | 5 | |Source| |PyPI| |Travis| |Docs| |Codecov| |Codacy| |Format| |License| 6 | 7 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo-cidar) 8 | [![PyPI](https://img.shields.io/pypi/v/moclo-cidar.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-cidar) 9 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 10 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 11 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 12 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 13 | [![Format](https://img.shields.io/pypi/format/moclo-cidar.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-cidar) 14 | [![Versions](https://img.shields.io/pypi/pyversions/moclo-cidar.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo-cidar) 15 | [![License](https://img.shields.io/pypi/l/moclo-cidar.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 16 | 17 | ## 📚 Documentation 18 | 19 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 20 | and built against the latest commit of the development repository. It contains 21 | a comprehensive API reference as well as examples compiled from Jupyter 22 | notebooks at each build. 23 | 24 | 25 | ## 🛠️ Installation 26 | 27 | This package is available as a *wheel*, and can be installed with ``pip``: 28 | ```console 29 | $ pip install --user moclo-cidar 30 | ``` 31 | 32 | To see more ways of installing, head over to the 33 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 34 | page of the online documentation. 35 | 36 | 37 | ## 📜 License 38 | 39 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 40 | 41 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 42 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 43 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 44 | at the Institut Pasteur of Paris during the summer of 2018.* 45 | -------------------------------------------------------------------------------- /docs/source/theory/typed.rst: -------------------------------------------------------------------------------- 1 | Typed Modular Cloning System 2 | ============================ 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | 8 | 9 | System Definition 10 | ----------------- 11 | 12 | 13 | .. admonition:: Definition 14 | :class: math-definition 15 | 16 | Given a genetic alphabet :math:`\langle \Sigma, \sim \rangle`, a 17 | Typed Modular Cloning System :math:`S` is defined as a mathematical 18 | sequence 19 | 20 | .. math:: (M_l,\ V_l,\ \mathcal{M}_l,\ \mathcal{V}_l,\ e_l)_ {\ l\ \ge -1} 21 | 22 | where: 23 | 24 | * :math:`(M_l, V_l, e_l)_{l \ge -1}` is a standard Modular Cloning System 25 | * :math:`\mathcal{M}_l \subseteq \mathcal{P}(M_l) \to \mathcal{P}(M_l)` 26 | is the set of *module types* of level :math:`l` 27 | * :math:`\mathcal{V}_l \subseteq \mathcal{P}(V_l) \to \mathcal{P}(V_l)` 28 | is the set of *vector types* of level :math:`l` 29 | 30 | 31 | 32 | Types 33 | ----- 34 | 35 | .. admonition:: Definition 36 | :class: math-definition 37 | 38 | :math:`\forall l \ge -1`, we define types using their signatures (*i.e.* the 39 | sets of upstream and downstream overhangs of elements using this type): 40 | 41 | .. math:: 42 | 43 | \begin{array}{ll} 44 | \forall t \in \mathcal{M}_l,& \begin{cases} 45 | Up(t) &= \bigcup_{m \in t(M_l)} \{ up(m) \} \\ 46 | Down(t) &= \bigcup_{m \in t(M_l)} \{ down(m) \} 47 | \end{cases} \\ 48 | \forall t \in \mathcal{V}_l,& \begin{cases} 49 | Up(t) &= \bigcup_{v \in t(V_l)} \{ up(v) \} \\ 50 | Down(t) &= \bigcup_{v \in t(V_l)} \{ down(v) \} 51 | \end{cases} 52 | \end{array} 53 | 54 | 55 | .. admonition:: Corollary 56 | :class: math-property 57 | 58 | :math:`\forall l \ge -1`, 59 | 60 | .. math:: 61 | 62 | \begin{array}{lll} 63 | \forall t \in \mathcal{M}_l,&\ t(M_l) &= \{ m \in M_l\ |\ up(m) \in Up(t),\ down(m) \in Down(t) \} \\ 64 | \forall t \in \mathcal{V}_l,&\ t(V_l) &= \{ v \in V_l\ |\ up(v) \in Up(t),\ down(v) \in Down(t) \} 65 | \end{array} 66 | 67 | 68 | .. admonition:: Property: *Structural equivalence of module types* 69 | :class: math-property 70 | 71 | Given a valid (*resp.* unambiguous) (*resp.* complete) assembly 72 | 73 | .. math:: 74 | 75 | m_1 + \dots + m_k + v \xrightarrow{e_l} A \subset (\Sigma^\star \cup \Sigma^{(c)}) 76 | 77 | then if there exist :math:`t \in \mathcal{M}_l` such that 78 | 79 | .. math:: 80 | 81 | \begin{cases} 82 | \lvert Up(t) \rvert = \lvert Down(t) \rvert = 1 \\ 83 | m_1 \in t(M_l) 84 | \end{cases} 85 | 86 | then :math:`\forall m_1\prime \in t(M_l)`, 87 | 88 | .. math:: 89 | 90 | m_1\prime + \dots + m_k + v \xrightarrow{e_l} A \subset (\Sigma^\star \cup \Sigma^{(c)}) 91 | 92 | is valid (*resp.* unambiguous) (*resp.* complete). 93 | -------------------------------------------------------------------------------- /moclo/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo 3 | version = attr: moclo.__version__ 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/ 7 | description = A Python implementation of the MoClo system logic. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/ 30 | Addgene Guide = https://www.addgene.org/cloning/moclo/ 31 | 32 | [options] 33 | zip_safe = true 34 | include_package_data = true 35 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 36 | packages = find: 37 | test_suite = tests 38 | setup_requires = 39 | setuptools >=39.2 40 | install_requires = 41 | biopython ~=1.78 42 | property-cached ~=1.4 43 | fs ~=2.1 44 | six ~=1.10 45 | typing ~=3.6 ; python_version < '3.6' 46 | bz2file ~=0.98 ; python_version < '3' 47 | 48 | [bdist_wheel] 49 | universal = 1 50 | 51 | [check] 52 | metadata = true 53 | restructuredtext = true 54 | strict = true 55 | 56 | [sdist] 57 | formats = zip 58 | 59 | [coverage:report] 60 | show_missing = true 61 | exclude_lines = 62 | pragma: no cover 63 | if typing.TYPE_CHECKING: 64 | @abc.abstractmethod 65 | @abc.abstractproperty 66 | raise NotImplementedError 67 | return NotImplemented 68 | 69 | [green] 70 | file-pattern = test_*.py 71 | verbose = 2 72 | no-skip-report = true 73 | quiet-stdout = true 74 | run-coverage = true 75 | 76 | [pydocstyle] 77 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 78 | match = (?!test)(?!setup)[^\._].*\.py 79 | inherit = false 80 | ignore = D200, D203, D213, D406, D407 # Google conventions 81 | 82 | [flake8] 83 | max-line-length = 99 84 | doctests = True 85 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 86 | ignore = D200, D203, D213, D406, D407 # Google conventions 87 | 88 | [mypy] 89 | ignore_missing_imports = true 90 | 91 | [mypy-moclo.*] 92 | disallow_any_decorated = false 93 | disallow_any_generics = false 94 | disallow_any_unimported = true 95 | disallow_subclassing_any = true 96 | disallow_untyped_calls = false 97 | disallow_untyped_defs = false 98 | ignore_missing_imports = false 99 | warn_unused_ignores = false 100 | warn_return_any = false 101 | -------------------------------------------------------------------------------- /moclo-cidar/moclo/registry/cidar.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """CIDAR ToolKit sequences registry. 3 | 4 | Sequences were obtained from two sources: 5 | * The CIDAR plasmid files distributed with the kit in a zip archive, 6 | available under the *Protocol & Resources* tab of the CIDAR repository. 7 | * The individual plasmid files from their dedicated AddGene webpages, using 8 | the *full depositor* sequences. Missing sequences (*DVA_AE* and *DVA_AF*) 9 | were obtained by editing the overhangs of *DVA_EF*. 10 | 11 | In case of mismatch between the two sources, the Zip sequences were used 12 | preferably. 13 | 14 | Plasmids were rotated to share the same origin, using the start of the 15 | *BioBrick* prefix as a reference location. This ensures no feature overlaps 16 | the zero coordinate, which was the case beforehand, to ensure a complete 17 | Biopython compatibility. 18 | 19 | Common features were colored using the same palette as in the `Yeast ToolKit`. 20 | *AmpR* and *KanR* received additional cross-references from Swiss-Prot, and 21 | a *AmpR* and *KanR* promoter features were added, based on the sequence of 22 | YTK parts. Promoters, RBS and terminators feature types were changed from 23 | *misc_feature* to the expected type. 24 | 25 | See Also: 26 | The annotation script running on Python 3 in the `GitHub repository 27 | _` 28 | 29 | """ 30 | from __future__ import absolute_import 31 | from __future__ import unicode_literals 32 | 33 | import re 34 | 35 | import six 36 | 37 | from ..kits import cidar 38 | from .base import EmbeddedRegistry 39 | from ._utils import find_resistance 40 | 41 | 42 | class CIDARRegistry(EmbeddedRegistry): 43 | 44 | _module = __name__ 45 | _file = "cidar.tar.gz" 46 | 47 | _ENTITY_RX = re.compile(r"MoClo (.*): ([^\-\[\(]*)") 48 | 49 | _CLASSES = { 50 | "Cassette Vector": cidar.CIDARCassetteVector, 51 | "Entry Vector": cidar.CIDAREntryVector, 52 | "Device": cidar.CIDARDevice, 53 | "Transcriptional Unit": cidar.CIDARCassette, 54 | "Basic Part": cidar.CIDARPart, 55 | } 56 | 57 | _TYPES = { 58 | "Double terminator": cidar.CIDARTerminator, 59 | "RBS": cidar.CIDARRibosomeBindingSite, 60 | "CDS": cidar.CIDARCodingSequence, 61 | "Controllable promoter": cidar.CIDARPromoter, 62 | "Constitutive promoter": cidar.CIDARPromoter, 63 | } 64 | 65 | def _load_entity(self, record): 66 | match = self._ENTITY_RX.match(record.description) 67 | if match is not None: 68 | class_, type_ = map(six.text_type.strip, match.groups()) 69 | if class_ == "Destination Vector": 70 | if record.id.startswith("DVA"): 71 | class_ = "Entry Vector" 72 | elif record.id.startswith("DVK"): 73 | class_ = "Cassette Vector" 74 | if class_ != "Basic Part": 75 | return self._CLASSES[class_](record) 76 | if type_ in self._TYPES: 77 | return self._TYPES[type_](record) 78 | raise RuntimeError("could not find type of '{}'".format(record.id)) 79 | -------------------------------------------------------------------------------- /moclo-moclo/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo-moclo 3 | version = file: moclo/kits/moclo.version 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/tree/master/moclo-ig 7 | description = Original MoClo implementation for the base moclo library. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo-moclo.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/kits/moclo/index.html 30 | Addgene Guide = https://www.addgene.org/cloning/moclo/marillonnet/ 31 | Icon Genetics Website = https://www.icongenetics.com/ 32 | 33 | [options] 34 | zip_safe = true 35 | include_package_data = true 36 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 37 | packages = moclo.kits 38 | test_suite = tests 39 | setup_requires = 40 | setuptools >=39.2 41 | install_requires = 42 | moclo ~=0.4.0 43 | six ~=1.10 44 | 45 | [bdist_wheel] 46 | universal = 1 47 | 48 | [check] 49 | metadata = true 50 | restructuredtext = true 51 | strict = true 52 | 53 | [sdist] 54 | formats = zip 55 | 56 | [options.package_data] 57 | * = *.version 58 | 59 | [coverage:report] 60 | show_missing = true 61 | exclude_lines = 62 | pragma: no cover 63 | if typing.TYPE_CHECKING: 64 | @abc.abstractmethod 65 | @abc.abstractproperty 66 | raise NotImplementedError 67 | return NotImplemented 68 | 69 | [green] 70 | file-pattern = test_*.py 71 | verbose = 2 72 | no-skip-report = true 73 | quiet-stdout = true 74 | run-coverage = true 75 | 76 | [pydocstyle] 77 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 78 | match = (?!test)(?!setup)[^\._].*\.py 79 | inherit = false 80 | ignore = D200, D203, D213, D406, D407 # Google conventions 81 | 82 | [flake8] 83 | max-line-length = 99 84 | doctests = True 85 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 86 | ignore = D200, D203, D213, D406, D407 # Google conventions 87 | 88 | [mypy] 89 | ignore_missing_imports = true 90 | 91 | [mypy-moclo.*] 92 | disallow_any_decorated = false 93 | disallow_any_generics = false 94 | disallow_any_unimported = true 95 | disallow_subclassing_any = true 96 | disallow_untyped_calls = false 97 | disallow_untyped_defs = false 98 | ignore_missing_imports = false 99 | warn_unused_ignores = false 100 | warn_return_any = false 101 | -------------------------------------------------------------------------------- /moclo-ytk/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo-ytk 3 | version = file: moclo/kits/ytk.version 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/tree/master/moclo-ytk 7 | description = Yeast ToolKit implementation for the base moclo library. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo, yeast 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo-ytk.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/kits/ytk/index.html 30 | Addgene Guide = https://www.addgene.org/kits/moclo-ytk/ 31 | 32 | [options] 33 | zip_safe = true 34 | include_package_data = true 35 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 36 | packages = moclo.kits, moclo.registry 37 | test_suite = tests 38 | setup_requires = 39 | setuptools >=39.2 40 | install_requires = 41 | moclo ~=0.4.0 42 | six ~=1.10 43 | 44 | [bdist_wheel] 45 | universal = 1 46 | 47 | [check] 48 | metadata = true 49 | restructuredtext = true 50 | strict = true 51 | 52 | [sdist] 53 | formats = zip 54 | 55 | [options.package_data] 56 | moclo.kits.ytk = ytk.version 57 | moclo.registry = ytk.tar.gz, ptk.tar.gz 58 | 59 | [coverage:report] 60 | show_missing = true 61 | exclude_lines = 62 | pragma: no cover 63 | if typing.TYPE_CHECKING: 64 | @abc.abstractmethod 65 | @abc.abstractproperty 66 | raise NotImplementedError 67 | return NotImplemented 68 | 69 | [green] 70 | file-pattern = test_*.py 71 | verbose = 2 72 | no-skip-report = true 73 | quiet-stdout = true 74 | run-coverage = true 75 | 76 | [pydocstyle] 77 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 78 | match = (?!test)(?!setup)[^\._].*\.py 79 | inherit = false 80 | ignore = D200, D203, D213, D406, D407 # Google conventions 81 | 82 | [flake8] 83 | max-line-length = 99 84 | doctests = True 85 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 86 | ignore = D200, D203, D213, D406, D407 # Google conventions 87 | 88 | [mypy] 89 | ignore_missing_imports = true 90 | 91 | [mypy-moclo.*] 92 | disallow_any_decorated = false 93 | disallow_any_generics = false 94 | disallow_any_unimported = true 95 | disallow_subclassing_any = true 96 | disallow_untyped_calls = false 97 | disallow_untyped_defs = false 98 | ignore_missing_imports = false 99 | warn_unused_ignores = false 100 | warn_return_any = false 101 | -------------------------------------------------------------------------------- /moclo-ytk/moclo/registry/ytk.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Yeast ToolKit and Pichia ToolKit sequences registry. 3 | 4 | Yeast ToolKit: 5 | Sequences were obtained from two different AddGene sources: 6 | * The YTK plasmid files distributed with the kit in a zip archive, 7 | available under the *Protocol & Resources* tab of the YTK repository. 8 | * The individual plasmid files from their dedicated AddGene webpages, 9 | using the *full depositor* sequences. 10 | 11 | Records were then merged using a Python script and ``biopython``; details 12 | can be found in the history of the ``git`` repository. Some annotations 13 | were also added to common genes and sequences (such as the Chloramphenicol 14 | resistance cassette `CmR `_, or 15 | the `H4 ARS consensus `_). 16 | Duplicated features refering to the same location were merged directly, 17 | and duplicated features with unidentical locations were manually reviewed. 18 | 19 | Pichia ToolKit: 20 | Sequences were obtained from two different AddGene sources: 21 | * The PTK plasmid files available on each AddGene plasmid webpage as 22 | *Supplemental Documents* 23 | * The indiviual plasmid files from their dedicated AddGene webpages, 24 | using the *full AddGene* sequences. 25 | 26 | Since both sequences did not share the same origins, plasmids were rotated 27 | for the upstream *BsaI* recognition site to span on nucleotides 3 to 11, 28 | in order for the PTK plasmids to be organized like the YTK plasmids. Using 29 | ``biopython``, sequences were annotated using a custom annotation script 30 | (see `source _`). 31 | 32 | """ 33 | from __future__ import absolute_import 34 | from __future__ import unicode_literals 35 | 36 | import six 37 | 38 | from ..kits import ytk 39 | from .base import EmbeddedRegistry 40 | 41 | 42 | class YTKRegistry(EmbeddedRegistry): 43 | 44 | _module = __name__ 45 | _file = "ytk.tar.gz" 46 | 47 | _types = { 48 | "1": ytk.YTKPart1, 49 | "2": ytk.YTKPart2, 50 | "3": ytk.YTKPart3, 51 | "3a": ytk.YTKPart3a, 52 | "3b": ytk.YTKPart3b, 53 | "4": ytk.YTKPart4, 54 | "4a": ytk.YTKPart4a, 55 | "4b": ytk.YTKPart4b, 56 | "234": ytk.YTKPart234, 57 | "234r": ytk.YTKPart234r, 58 | "5": ytk.YTKPart5, 59 | "6": ytk.YTKPart6, 60 | "7": ytk.YTKPart7, 61 | "8": ytk.YTKPart8, 62 | "8a": ytk.YTKPart8a, 63 | "8b": ytk.YTKPart8b, 64 | "678": ytk.YTKPart678, 65 | "cassette vector": ytk.YTKCassetteVector, 66 | "entry vector": ytk.YTKEntryVector, 67 | } 68 | 69 | def _load_entity(self, record): 70 | comments = record.annotations["comment"].splitlines() 71 | hint = next(c for c in comments if c.startswith("YTK:")) 72 | comments.remove(hint) 73 | record.annotations["comment"] = "\n".join(comments) 74 | _, type_ = hint.strip().split(":", 1) 75 | return self._types[type_](record) 76 | 77 | 78 | class PTKRegistry(YTKRegistry): 79 | 80 | _file = "ptk.tar.gz" 81 | -------------------------------------------------------------------------------- /moclo-ecoflex/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo-ecoflex 3 | version = file: moclo/kits/ecoflex.version 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/tree/master/moclo-ecoflex 7 | description = EcoFlex implementation for the base moclo library. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo-ecoflex.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/kits/ecoflex/index.html 30 | Addgene Guide = https://www.addgene.org/cloning/moclo/freemont-ecoflex/ 31 | 32 | [options] 33 | zip_safe = true 34 | include_package_data = true 35 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 36 | packages = moclo.kits, moclo.registry 37 | test_suite = tests 38 | setup_requires = 39 | setuptools >=39.2 40 | install_requires = 41 | moclo ~=0.4.0 42 | six ~=1.10 43 | 44 | [bdist_wheel] 45 | universal = 1 46 | 47 | [check] 48 | metadata = true 49 | restructuredtext = true 50 | strict = true 51 | 52 | [sdist] 53 | formats = zip 54 | 55 | [options.package_data] 56 | moclo.kits.ecoflex = ecoflex.version 57 | moclo.registry = ecoflex.tar.gz 58 | 59 | [coverage:report] 60 | show_missing = true 61 | exclude_lines = 62 | pragma: no cover 63 | if typing.TYPE_CHECKING: 64 | @abc.abstractmethod 65 | @abc.abstractproperty 66 | raise NotImplementedError 67 | return NotImplemented 68 | 69 | [green] 70 | file-pattern = test_*.py 71 | verbose = 2 72 | no-skip-report = true 73 | quiet-stdout = true 74 | run-coverage = true 75 | 76 | [pydocstyle] 77 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 78 | match = (?!test)(?!setup)[^\._].*\.py 79 | inherit = false 80 | ignore = D200, D203, D213, D406, D407 # Google conventions 81 | 82 | [flake8] 83 | max-line-length = 99 84 | doctests = True 85 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 86 | ignore = D200, D203, D213, D406, D407 # Google conventions 87 | 88 | [mypy] 89 | ignore_missing_imports = true 90 | 91 | [mypy-moclo.*] 92 | disallow_any_decorated = false 93 | disallow_any_generics = false 94 | disallow_any_unimported = true 95 | disallow_subclassing_any = true 96 | disallow_untyped_calls = false 97 | disallow_untyped_defs = false 98 | ignore_missing_imports = false 99 | warn_unused_ignores = false 100 | warn_return_any = false 101 | -------------------------------------------------------------------------------- /moclo-cidar/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo-cidar 3 | version = file: moclo/kits/cidar.version 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/tree/master/moclo-cidar 7 | description = CIDAR implementation for the base moclo library. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo-cidar.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/kits/cidar/index.html 30 | Addgene Guide = https://www.addgene.org/cloning/moclo/densmore/ 31 | CIDAR Website = http://cidarlab.org/ 32 | 33 | [options] 34 | zip_safe = true 35 | include_package_data = true 36 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 37 | packages = moclo.kits, moclo.registry 38 | test_suite = tests 39 | setup_requires = 40 | setuptools >=39.2 41 | install_requires = 42 | moclo ~=0.4.0 43 | six ~=1.10 44 | 45 | [bdist_wheel] 46 | universal = 1 47 | 48 | [check] 49 | metadata = true 50 | restructuredtext = true 51 | strict = true 52 | 53 | [sdist] 54 | formats = zip 55 | 56 | [options.package_data] 57 | moclo.kits.cidar = cidar.version 58 | moclo.registry = cidar.tar.gz 59 | 60 | [coverage:report] 61 | show_missing = true 62 | exclude_lines = 63 | pragma: no cover 64 | if typing.TYPE_CHECKING: 65 | @abc.abstractmethod 66 | @abc.abstractproperty 67 | raise NotImplementedError 68 | return NotImplemented 69 | 70 | [green] 71 | file-pattern = test_*.py 72 | verbose = 2 73 | no-skip-report = true 74 | quiet-stdout = true 75 | run-coverage = true 76 | 77 | [pydocstyle] 78 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 79 | match = (?!test)(?!setup)[^\._].*\.py 80 | inherit = false 81 | ignore = D200, D203, D213, D406, D407 # Google conventions 82 | 83 | [flake8] 84 | max-line-length = 99 85 | doctests = True 86 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 87 | ignore = D200, D203, D213, D406, D407 # Google conventions 88 | 89 | [mypy] 90 | ignore_missing_imports = true 91 | 92 | [mypy-moclo.*] 93 | disallow_any_decorated = false 94 | disallow_any_generics = false 95 | disallow_any_unimported = true 96 | disallow_subclassing_any = true 97 | disallow_untyped_calls = false 98 | disallow_untyped_defs = false 99 | ignore_missing_imports = false 100 | warn_unused_ignores = false 101 | warn_return_any = false 102 | -------------------------------------------------------------------------------- /moclo-plant/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = moclo-plant 3 | version = file: moclo/kits/plant.version 4 | author = Martin Larralde 5 | author_email = martin.larralde@ens-paris-saclay.fr 6 | home_page = https://github.com/althonos/moclo/tree/master/moclo-ig 7 | description = Plant Parts Kit implementation for the base moclo library. 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | license = MIT 11 | license_file = COPYING 12 | platform = any 13 | keywords = bioinformatics, synthetic biology, modular cloning, moclo 14 | classifiers = 15 | Development Status :: 3 - Alpha 16 | Intended Audience :: Science/Research 17 | License :: OSI Approved :: MIT License 18 | Operating System :: OS Independent 19 | Programming Language :: Python 20 | Programming Language :: Python :: 3.6 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Scientific/Engineering :: Bio-Informatics 26 | project_urls = 27 | Bug Reports = https://github.com/althonos/moclo/issues 28 | Changelog = https://moclo.readthedocs.io/en/latest/changes/moclo-moclo.html 29 | Documentation = https://moclo.readthedocs.io/en/latest/kits/moclo/index.html 30 | Addgene Guide = https://www.addgene.org/cloning/moclo/marillonnet/ 31 | Icon Genetics Website = https://www.icongenetics.com/ 32 | 33 | [options] 34 | zip_safe = true 35 | include_package_data = true 36 | python_requires = >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.5.1 37 | packages = moclo.kits, moclo.registry 38 | test_suite = tests 39 | setup_requires = 40 | setuptools >=39.2 41 | install_requires = 42 | moclo ~=0.4.0 43 | moclo-moclo ~=0.1.0 44 | six ~=1.10 45 | 46 | [bdist_wheel] 47 | universal = 1 48 | 49 | [check] 50 | metadata = true 51 | restructuredtext = true 52 | strict = true 53 | 54 | [sdist] 55 | formats = zip 56 | 57 | [options.package_data] 58 | moclo.kits.plant = plant.version 59 | moclo.registry = plant.tar.gz 60 | 61 | [coverage:report] 62 | show_missing = true 63 | exclude_lines = 64 | pragma: no cover 65 | if typing.TYPE_CHECKING: 66 | @abc.abstractmethod 67 | @abc.abstractproperty 68 | raise NotImplementedError 69 | return NotImplemented 70 | 71 | [green] 72 | file-pattern = test_*.py 73 | verbose = 2 74 | no-skip-report = true 75 | quiet-stdout = true 76 | run-coverage = true 77 | 78 | [pydocstyle] 79 | match-dir = (?!tests)(?!resources)(?!docs)[^\.].* 80 | match = (?!test)(?!setup)[^\._].*\.py 81 | inherit = false 82 | ignore = D200, D203, D213, D406, D407 # Google conventions 83 | 84 | [flake8] 85 | max-line-length = 99 86 | doctests = True 87 | exclude = .git, .eggs, __pycache__, tests/, docs/, build/, dist/ 88 | ignore = D200, D203, D213, D406, D407 # Google conventions 89 | 90 | [mypy] 91 | ignore_missing_imports = true 92 | 93 | [mypy-moclo.*] 94 | disallow_any_decorated = false 95 | disallow_any_generics = false 96 | disallow_any_unimported = true 97 | disallow_subclassing_any = true 98 | disallow_untyped_calls = false 99 | disallow_untyped_defs = false 100 | ignore_missing_imports = false 101 | warn_unused_ignores = false 102 | warn_return_any = false 103 | -------------------------------------------------------------------------------- /moclo/moclo/errors.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import typing 4 | 5 | import six 6 | 7 | if typing.TYPE_CHECKING: 8 | from typing import Any # noqa: F401 9 | from Bio.Seq import Seq # noqa: F401 10 | from .base.modules import AbstractModule # noqa: F401 11 | 12 | 13 | class MocloError(Exception): 14 | """Base class for all MoClo-related exceptions. 15 | """ 16 | 17 | pass 18 | 19 | 20 | @six.python_2_unicode_compatible 21 | class InvalidSequence(ValueError, MocloError): 22 | """Invalid sequence provided. 23 | """ 24 | 25 | _msg = "invalid sequence: {}" 26 | 27 | def __init__(self, sequence, exc=None, details=None): 28 | self.sequence = sequence 29 | self.exc = exc 30 | self.details = details 31 | 32 | def __str__(self): 33 | s = self._msg 34 | if self.details is not None: 35 | s = "".join([s, " ", "(", self.details, ")"]) 36 | return s.format(self.sequence) 37 | 38 | 39 | class IllegalSite(InvalidSequence): 40 | """Sequence with illegal site provided. 41 | """ 42 | 43 | _msg = "illegal site in sequence: {}" 44 | 45 | 46 | class AssemblyError(MocloError, RuntimeError): 47 | """Assembly-specific run-time error. 48 | """ 49 | 50 | 51 | @six.python_2_unicode_compatible 52 | class DuplicateModules(AssemblyError): 53 | """Several modules share the same overhangs. 54 | """ 55 | 56 | def __init__(self, *duplicates, **options): 57 | # type: (*AbstractModule, **Any) -> None 58 | self.duplicates = duplicates 59 | self.details = options.pop("details", None) 60 | 61 | def __str__(self): 62 | s = "duplicate modules: {}" 63 | if self.details is not None: 64 | s = "".join([s, " ", "(", self.details, ")"]) 65 | return s.format(", ".join(d.record.id for d in self.duplicates)) 66 | 67 | 68 | @six.python_2_unicode_compatible 69 | class MissingModule(AssemblyError): 70 | """A module is missing in the assembly. 71 | """ 72 | 73 | def __init__(self, start_overhang, **options): 74 | self.start_overhang = start_overhang 75 | self.details = options.pop("details", None) 76 | 77 | def __str__(self): 78 | s = "no module with '{}' start overhang" 79 | if self.details is not None: 80 | s = "".join([s, " ", "(", self.details, ")"]) 81 | return s.format(self.start_overhang) 82 | 83 | 84 | class AssemblyWarning(MocloError, Warning): 85 | """Assembly-specific run-time warning. 86 | 87 | Warnings can be turned into errors using the `warnings.catch_warnings` 88 | decorator combined to `warnings.simplefilter` with `action` set to 89 | `"error"`. 90 | """ 91 | 92 | 93 | @six.python_2_unicode_compatible 94 | class UnusedModules(AssemblyWarning): 95 | """Not all modules were used during assembly. 96 | """ 97 | 98 | def __init__(self, *remaining, **options): 99 | self.remaining = remaining 100 | self.details = options.pop("details", None) 101 | 102 | def __str__(self): 103 | s = "unused: {}" 104 | if self.details is not None: 105 | s = "".join([s, " ", "(", str(self.details), ")"]) 106 | return s.format(", ".join(r.record.id for r in self.remaining)) 107 | -------------------------------------------------------------------------------- /tests/test_ytk.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from moclo.kits import ytk 8 | from moclo.registry.base import CombinedRegistry 9 | from moclo.registry.ytk import YTKRegistry, PTKRegistry 10 | 11 | from ._utils import AssemblyTestCase, PartsMetaCase, build_registries 12 | 13 | # --- Test Suite Metaclass --------------------------------------------------- 14 | 15 | _Meta = PartsMetaCase( 16 | "YTK", lambda: CombinedRegistry() << YTKRegistry() << PTKRegistry(), __name__ 17 | ) 18 | 19 | 20 | def exclude_96(item): 21 | return item.id == "pYTK096" 22 | 23 | 24 | # --- Test YTK Parts --------------------------------------------------------- 25 | 26 | TestYTKPart1 = _Meta(ytk.YTKPart1, "1") 27 | TestYTKPart2 = _Meta(ytk.YTKPart2, "2") 28 | TestYTKPart3 = _Meta(ytk.YTKPart3, "3") 29 | TestYTKPart3a = _Meta(ytk.YTKPart3a, "3a") 30 | TestYTKPart3b = _Meta(ytk.YTKPart3b, "3b") 31 | TestYTKPart4 = _Meta(ytk.YTKPart4, "4") 32 | TestYTKPart4a = _Meta(ytk.YTKPart4a, "4a") 33 | TestYTKPart4b = _Meta(ytk.YTKPart4b, "4b") 34 | TestYTKPart234 = _Meta(ytk.YTKPart234, "234") 35 | TestYTKPart234r = _Meta(ytk.YTKPart234r, "234r", exclude_96) 36 | TestYTKPart5 = _Meta(ytk.YTKPart5, "5") 37 | TestYTKPart6 = _Meta(ytk.YTKPart6, "6") 38 | TestYTKPart7 = _Meta(ytk.YTKPart7, "7") 39 | TestYTKPart8 = _Meta(ytk.YTKPart8, "8") 40 | TestYTKPart8a = _Meta(ytk.YTKPart8a, "8a") 41 | TestYTKPart8b = _Meta(ytk.YTKPart8b, "8b") 42 | TestYTKPart678 = _Meta(ytk.YTKPart678, "678") 43 | 44 | # --- Test YTK Vectors ------------------------------------------------------- 45 | 46 | # TestYTKEntryVector = _Meta(ytk.YTKEntryVector, "EntryVector") 47 | # TestYTKCassetteVector = _Meta(ytk.YTKCassetteVector, "CassetteVector") 48 | # TestYTKDeviceVector = _Meta(ytk.YTKDeviceVector, "DeviceVector") 49 | 50 | # --- Test YTK Assemblies ---------------------------------------------------- 51 | 52 | # Generate test cases based on test assembly + reference assembly 53 | 54 | 55 | class TestYTKAssembly(AssemblyTestCase, unittest.TestCase): 56 | @classmethod 57 | def setUpClass(cls): 58 | build_registries("ytk") 59 | 60 | def test_ytk_device(self): 61 | """Check a YTK device assembly using several cassettes. 62 | 63 | DNA sequences courtesy of InBio's member `Sebastián Sosa Carrillo 64 | `_. 65 | """ 66 | result, vector, modules = self.load_data("ytk_device") 67 | self.assertAssembly( 68 | ytk.YTKDeviceVector(vector), map(ytk.YTKCassette, modules.values()), result 69 | ) 70 | 71 | def test_ytk_integration_vector(self): 72 | """Check the YTK integration vector assembly from the reference paper. 73 | """ 74 | result, vector, modules = self.load_data("ytk_integration_vector") 75 | 76 | modules["pYTK008.gb"] = ytk.YTKPart1(modules["pYTK008.gb"]) 77 | modules["pYTK047.gb"] = ytk.YTKPart234r(modules["pYTK047.gb"]) 78 | modules["pYTK073.gb"] = ytk.YTKPart5(modules["pYTK073.gb"]) 79 | modules["pYTK074.gb"] = ytk.YTKPart6(modules["pYTK074.gb"]) 80 | modules["pYTK086.gb"] = ytk.YTKPart7(modules["pYTK086.gb"]) 81 | modules["pYTK092.gb"] = ytk.YTKPart8b(modules["pYTK092.gb"]) 82 | 83 | self.assertAssembly(ytk.YTKPart8a(vector), modules.values(), result) 84 | -------------------------------------------------------------------------------- /notebook/ytk/YTK Protocol.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# YTK Protocol (Parts $\\rightarrow$ Cassette)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Initial imports:\n", 15 | "\n", 16 | "See [requirements](misc/requirements.txt) file to see the required packages to run this notebook." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "import lib" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "registry = lib.load_embedded_registry(ptk=False) # set to True to load Pichia ToolKit" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# Run this cell if you have an ELabFTW server to connect to\n", 44 | "lib.load_elab_registry(\n", 45 | " registry,\n", 46 | " server=\"https://elab.server.org:3148\", # the server with hostname and port\n", 47 | " token=\"\", # generate one from your ELabFTW profile\n", 48 | " category='Plasmids', # the category of inventory items to use\n", 49 | " include_tags={'YTK'}, # a set of tags to filter the inventory with\n", 50 | ")" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "plasmids = lib.ask_plasmids()" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": { 66 | "scrolled": false 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "parts = lib.ask_parts(registry, plasmids)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "assemblies = lib.validate_assemblies(registry, parts)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "strains, concentrations = lib.ask_concentrations(assemblies)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "lib.dilution_table(strains, concentrations)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "lib.generate_gb_files(assemblies)" 107 | ] 108 | } 109 | ], 110 | "metadata": { 111 | "kernelspec": { 112 | "display_name": "Python 3", 113 | "language": "python", 114 | "name": "python3" 115 | }, 116 | "language_info": { 117 | "codemirror_mode": { 118 | "name": "ipython", 119 | "version": 3 120 | }, 121 | "file_extension": ".py", 122 | "mimetype": "text/x-python", 123 | "name": "python", 124 | "nbconvert_exporter": "python", 125 | "pygments_lexer": "ipython3", 126 | "version": "3.7.0" 127 | } 128 | }, 129 | "nbformat": 4, 130 | "nbformat_minor": 2 131 | } 132 | -------------------------------------------------------------------------------- /moclo/moclo/regex.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import re 6 | import typing 7 | 8 | import Bio.Seq 9 | import Bio.SeqRecord 10 | import six 11 | 12 | from .record import CircularRecord 13 | 14 | if typing.TYPE_CHECKING: 15 | from typing import Mapping, Match, Optional, Sequence, Text, Tuple # noqa: F401 16 | 17 | 18 | # typing.TypeVar: a generic sequence 19 | _S = typing.TypeVar("_S", bound=typing.Sequence) 20 | 21 | 22 | class SeqMatch(typing.Generic[_S]): 23 | def __init__(self, match, rec, shift=0): 24 | # type: (Match, _S, int) -> None # type: ignore 25 | self.match = match 26 | self.shift = shift 27 | self.rec = rec # type: _S 28 | 29 | def end(self): 30 | # type: () -> int 31 | return self.match.end() 32 | 33 | def start(self): 34 | # type: () -> int 35 | return self.match.start() 36 | 37 | def span(self, index=0): 38 | # type: (int) -> Tuple[int, int] 39 | return self.match.span(index) 40 | 41 | def group(self, index=0): 42 | # type: (int) -> _S 43 | span = self.match.span(index) 44 | if span[1] >= span[0] >= len(self.rec): 45 | return self.rec[ 46 | span[0] % len(self.rec) : span[1] % len(self.rec) 47 | ] # type: ignore 48 | elif span[1] >= len(self.rec) > span[0]: 49 | return ( 50 | self.rec[: span[1] % len(self.rec)] + self.rec[span[0] :] 51 | ) # type: ignore 52 | else: 53 | return self.rec[span[0] : span[1]] # type: ignore 54 | 55 | 56 | class DNARegex(object): 57 | """A DNA pattern matcher. 58 | """ 59 | 60 | # dict: single-letter code to regex syntax for nucleotides 61 | _lettermap = { 62 | "B": "[CGT]", 63 | "D": "[AGT]", 64 | "H": "[ACT]", 65 | "K": "[GT]", 66 | "M": "[AC]", 67 | "N": "[ACGTN]", 68 | "R": "[AG]", 69 | "S": "[CG]", 70 | "V": "[ACG]", 71 | "W": "[AT]", 72 | "Y": "[CT]", 73 | } # type: Mapping[Text, Text] 74 | 75 | @classmethod 76 | def _transcribe(cls, pattern): 77 | # type: (Text) -> Text 78 | target = ["(?i)"] 79 | for letter in pattern: 80 | target.append(cls._lettermap.get(letter, letter)) 81 | return "".join(target) 82 | 83 | def __init__(self, pattern): 84 | # type: (Text) -> None 85 | self.pattern = pattern 86 | self.regex = re.compile(self._transcribe(pattern)) 87 | 88 | def search(self, string, pos=0, endpos=six.MAXSIZE, linear=True): 89 | # type: (_S, int, int) -> Optional[SeqMatch[_S]] 90 | 91 | if not isinstance(string, (Bio.Seq.Seq, Bio.SeqRecord.SeqRecord)): 92 | t = type(string).__name__ 93 | raise TypeError("can only match Seq or SeqRecord, not '{}'".format(t)) 94 | 95 | if isinstance(string, Bio.SeqRecord.SeqRecord): 96 | data = str(string.seq) 97 | else: 98 | data = str(string) 99 | 100 | if not linear or isinstance(string, CircularRecord): 101 | data *= 2 102 | 103 | for i in range(pos, min(len(string), endpos)): 104 | match = self.regex.match(data, i, i + len(string)) 105 | if match is not None: 106 | return SeqMatch(match, string) 107 | 108 | return None 109 | -------------------------------------------------------------------------------- /moclo/README.md: -------------------------------------------------------------------------------- 1 | # `moclo` 2 | 3 | *A Python implementation of the [MoClo](https://www.addgene.org/cloning/moclo/) system logic.* 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo/tree/master/moclo) 6 | [![PyPI](https://img.shields.io/pypi/v/moclo.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo) 7 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 8 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 9 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 10 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 11 | [![Format](https://img.shields.io/pypi/format/moclo.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo) 12 | [![Versions](https://img.shields.io/pypi/pyversions/moclo.svg?style=flat-square&maxAge=300)](https://pypi.python.org/pypi/moclo) 13 | [![License](https://img.shields.io/pypi/l/moclo.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 14 | 15 | 16 | ## 📚 Documentation 17 | 18 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 19 | and built against the latest commit of the development repository. It contains 20 | a comprehensive API reference as well as examples compiled from Jupyter 21 | notebooks at each build. 22 | 23 | 24 | ## 🛠️ Installation 25 | 26 | This package is available as a *wheel*, and can be installed with ``pip``: 27 | ```console 28 | $ pip install --user moclo 29 | ``` 30 | 31 | To see more ways of installing, head over to the 32 | [Installation](https://moclo.readthedocs.io/en/latest/install.html) 33 | page of the online documentation. 34 | 35 | 36 | ## 🧰 Kits 37 | 38 | By itself, `moclo` is not very useful. To be able to simulate MoClo assemblies 39 | you can install some of the following toolkits: 40 | 41 | - [`moclo-ytk`](https://pypi.org/project/moclo-ytk): MoClo Yeast ToolKit, 42 | *John Dueber Lab*, and Pichia ToolKit, *Volker Sieber Lab* 43 | - [`moclo-cidar`](https://pypi.org/project/moclo-cidar): MoClo CIDAR kit, 44 | *Douglas Densmore Lab* 45 | - [`moclo-ecoflex`](https://pypi.org/project/moclo-ecoflex): MoClo EcoFlex, 46 | *Paul Freemont Lab* 47 | - [`moclo-ig`](https://pypi.org/project/moclo-ig): Icon Genetics MoClo, 48 | *Sylvestre Marillonnet Lab* 49 | - [`moclo-gb3`](https://pypi.org/project/moclo-gb3): Golden Braid 3.0, 50 | *Diego Orzaez Lab* 51 | 52 | Toolkits ship with concrete implementation of the MoClo logic (using the DNA 53 | signatures and restriction enzymes from the reference paper), as well as official 54 | sequences obtained from [Addgene](https://www.addgene.org) and manually 55 | annotated with higher-quality features. These sequences can be accessed through 56 | the `moclo.registry` module, using the *registry* interface. 57 | 58 | 59 | ## 📜 License 60 | 61 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 62 | 63 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 64 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 65 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 66 | at the Institut Pasteur of Paris during the summer of 2018.* 67 | -------------------------------------------------------------------------------- /tests/test_assembly.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | import warnings 7 | 8 | from Bio.Seq import Seq 9 | from Bio.SeqRecord import SeqRecord 10 | from Bio.Restriction import BpiI 11 | 12 | from moclo import errors 13 | from moclo.record import CircularRecord 14 | from moclo.core.vectors import AbstractVector 15 | from moclo.core.modules import AbstractModule 16 | 17 | 18 | class TestAssembly(unittest.TestCase): 19 | class MockVector(AbstractVector): 20 | cutter = BpiI 21 | 22 | class MockModule(AbstractModule): 23 | cutter = BpiI 24 | 25 | def test_invalid_vector(self): 26 | """Assert an error is raised on a vector with invalid overhangs. 27 | """ 28 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTATGCGG") 29 | vector = self.MockVector(CircularRecord(seqv, "vector")) 30 | 31 | seqm = Seq("GAAGACTTATGCCACAATGCTTGTCTTC") 32 | module = self.MockModule(CircularRecord(seqm, "module")) 33 | 34 | # vector is invalid because both overhangs are the same 35 | self.assertRaises(errors.InvalidSequence, vector.assemble, module) 36 | 37 | def test_duplicate_modules(self): 38 | """Assert an error is raised when assembling with duplicate modules. 39 | """ 40 | # ATGC ---- CGTA 41 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 42 | vector = self.MockVector(CircularRecord(seqv, "vector")) 43 | 44 | # ATGC --- CGTA 45 | seqm1 = Seq("GAAGACTTATGCCACACGTATTGTCTTC") 46 | mod1 = self.MockModule(CircularRecord(seqm1, "mod1")) 47 | 48 | # CGTA --- ATGC 49 | seqm2 = Seq("GAAGACTTATGCTATACGTATTGTCTTC") 50 | mod2 = self.MockModule(CircularRecord(seqm2, "mod2")) 51 | 52 | with self.assertRaises(errors.DuplicateModules) as ctx: 53 | vector.assemble(mod1, mod2) 54 | self.assertEqual(set(ctx.exception.duplicates), {mod1, mod2}) 55 | self.assertEqual(ctx.exception.details, "same start overhang: 'ATGC'") 56 | msg = "duplicate modules: mod1, mod2 (same start overhang: 'ATGC')" 57 | self.assertEqual(str(ctx.exception), msg) 58 | 59 | def test_missing_module(self): 60 | """Assert an error is raised when a module is missing. 61 | """ 62 | # ATGC ---- CGTA 63 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 64 | vector = self.MockVector(CircularRecord(seqv, "vector")) 65 | 66 | # CGTA --- ATGA 67 | seqm1 = Seq("GAAGACTTATGACACACGTATTGTCTTC") 68 | mod1 = self.MockModule(CircularRecord(seqm1, "mod1")) 69 | 70 | with self.assertRaises(errors.MissingModule) as ctx: 71 | vector.assemble(mod1) 72 | msg = "no module with 'ATGC' start overhang" 73 | self.assertEqual(str(ctx.exception), msg) 74 | 75 | def test_unused_modules(self): 76 | """Assert an error is raised on unused modules during assembly. 77 | """ 78 | # ATGC ---- CGTA 79 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 80 | vector = self.MockVector(CircularRecord(seqv, "vector")) 81 | 82 | # CGTA --- ATGC 83 | seqm1 = Seq("GAAGACTTATGCTATACGTATTGTCTTC") 84 | mod1 = self.MockModule(CircularRecord(seqm1, "mod1")) 85 | 86 | # AAAA --- CCCC 87 | seqm2 = Seq("GAAGACTTAAAACACACCCCTTGTCTTC") 88 | mod2 = self.MockModule(CircularRecord(seqm2, "mod2")) 89 | 90 | with warnings.catch_warnings(record=True) as captured: 91 | vector.assemble(mod1, mod2) 92 | 93 | self.assertEqual(len(captured), 1) 94 | self.assertIsInstance(captured[0].message, errors.UnusedModules) 95 | self.assertEqual(captured[0].message.remaining, (mod2,)) 96 | self.assertEqual(str(captured[0].message), "unused: mod2") 97 | -------------------------------------------------------------------------------- /moclo/moclo/core/parts.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Moclo part classes. 3 | """ 4 | 5 | import typing 6 | 7 | from Bio.Seq import Seq 8 | 9 | from .._utils import isabstract 10 | from .modules import AbstractModule 11 | from .vectors import AbstractVector 12 | from ._structured import StructuredRecord 13 | from ._utils import cutter_check 14 | 15 | if typing.TYPE_CHECKING: 16 | from typing import Union # noqa: F401 17 | from Bio.SeqRecord import SeqRecord # noqa: F401 18 | 19 | 20 | __all__ = ["AbstractPart"] 21 | 22 | 23 | class AbstractPart(StructuredRecord): 24 | """An abstract modular cloning part. 25 | 26 | Parts can be either modules or vectors, but are determined by their 27 | flanking overhangs sequences, declared in the ``signature`` class 28 | attribute. The part structure is derived from the part class (module 29 | of vector), signature, and restriction enzyme. 30 | 31 | Example: 32 | >>> class ExamplePart(AbstractPart, Entry): 33 | ... cutter = BsaI 34 | ... signature = ('ATGC', 'ATTC') 35 | ... 36 | >>> ExamplePart.structure() 37 | 'GGTCTCN(ATGC)(NN*N)(ATTC)NGAGACC' 38 | 39 | """ 40 | 41 | cutter = NotImplemented 42 | signature = NotImplemented 43 | 44 | def __new__(cls, *args, **kwargs): 45 | cutter_check(cls.cutter, name=cls.__name__) 46 | return super(AbstractPart, cls).__new__(cls) 47 | 48 | @classmethod 49 | def structure(cls): 50 | # type: () -> Text 51 | """Get the part structure, as a DNA regex pattern. 52 | 53 | The structure of most parts can be obtained automatically from the 54 | part signature and the restriction enzyme used in the Golden Gate 55 | assembly. 56 | 57 | Warning: 58 | If overloading this method, the returned pattern must include 3 59 | capture groups to capture the following features: 60 | 61 | 1. The upstream (5') overhang sequence 62 | 2. The vector placeholder sequence 63 | 3. The downstream (3') overhang sequence 64 | 65 | """ 66 | 67 | if cls.signature is NotImplemented: 68 | raise NotImplementedError("no signature defined") 69 | 70 | up = cls.cutter.elucidate() 71 | down = str(Seq(up).reverse_complement()) 72 | ovhg = cls.cutter.ovhgseq 73 | upsig, downsig = cls.signature 74 | 75 | if cls.cutter.is_5overhang(): 76 | upsite = "^{}_".format(ovhg) 77 | downsite = "_{}^".format(Seq(ovhg).reverse_complement()) 78 | else: 79 | upsite = "_{}^".format(ovhg) 80 | downsite = "^{}_".format(Seq(ovhg).reverse_complement()) 81 | 82 | if issubclass(cls, AbstractModule): 83 | return "".join( 84 | [ 85 | up.replace(upsite, "({})(".format(upsig)), 86 | "N*", 87 | down.replace(downsite, ")({})".format(downsig)), 88 | ] 89 | ) 90 | elif issubclass(cls, AbstractVector): 91 | return "".join( 92 | [ 93 | down.replace(downsite, "({})(".format(downsig)), 94 | "N*", 95 | up.replace(upsite, ")({})".format(upsig)), 96 | ] 97 | ) 98 | else: 99 | raise RuntimeError("Part must be either a module or a vector!") 100 | 101 | @classmethod 102 | def characterize(cls, record): 103 | """Load the record in a concrete subclass of this type. 104 | """ 105 | classes = list(cls.__subclasses__()) 106 | if not isabstract(cls): 107 | classes.append(cls) 108 | for subclass in classes: 109 | entity = subclass(record) 110 | if entity.is_valid(): 111 | return entity 112 | raise RuntimeError("could not find the type for '{}'".format(record.id)) 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `moclo` [![Stars](https://img.shields.io/github/stars/althonos/moclo.svg?style=social&maxAge=3600&label=Star)](https://github.com/althonos/moclo/stargazers) 2 | 3 | *A Python implementation of the [MoClo](https://www.addgene.org/cloning/moclo/) system logic.* 4 | 5 | [![Source](https://img.shields.io/badge/source-GitHub-303030.svg?maxAge=3600&style=flat-square)](https://github.com/althonos/moclo) 6 | [![Build](https://img.shields.io/github/workflow/status/althonos/moclo/Test?style=flat-square&maxAge=3600)](https://github.com/althonos/moclo/actions) 7 | [![Docs](https://img.shields.io/readthedocs/moclo.svg?maxAge=3600&style=flat-square)](https://moclo.readthedocs.io/) 8 | [![Codecov](https://img.shields.io/codecov/c/github/althonos/moclo/master.svg?style=flat-square&maxAge=600)](https://codecov.io/gh/althonos/moclo) 9 | [![Codacy](https://img.shields.io/codacy/grade/5b29a9c0d91f4e82944a46997bd9a480/master.svg?style=flat-square&maxAge=300)](https://www.codacy.com/app/althonos/moclo) 10 | [![License](https://img.shields.io/pypi/l/moclo.svg?style=flat-square&maxAge=300)](https://choosealicense.com/licenses/mit/) 11 | [![DOI](https://img.shields.io/badge/doi-10.5281%2Fzenodo.1401815-blue.svg?style=flat-square&maxAge=31536000)](https://zenodo.org/badge/latestdoi/138012703) 12 | 13 | ## 📚 Documentation 14 | 15 | The documentation is hosted on [ReadTheDocs](https://moclo.readthedocs.org), 16 | and built against the latest commit of the development repository. It contains 17 | a comprehensive API reference as well as examples compiled from Jupyter 18 | notebooks at each build. 19 | 20 | 21 | ## 🔩 Base module 22 | 23 | The base logic is handled by the core [`moclo`](https://github.com/althonos/moclo/tree/master/moclo) 24 | module. It embeds an object model of the MoClo system logic, but does not enforce 25 | any specific sequence structure, and is not usable alone. You must install a kit 26 | (listed below) to be able to validate and compute assemblies. 27 | 28 | 29 | ## 🧰 Kits 30 | 31 | Additional kits can be installed separately depending on what's needed. The 32 | following implementations are available: 33 | 34 | * [Original MoClo (`moclo-moclo`)](https://github.com/althonos/moclo/tree/master/moclo-moclo) 35 | * [Yeast ToolKit and Pichia ToolKit (`moclo-ytk`)](https://github.com/althonos/moclo/tree/master/moclo-ytk) 36 | * [CIDAR Kit (`moclo-cidar`)](https://github.com/althonos/moclo/tree/master/moclo-cidar) 37 | * [EcoFlex Kit (`moclo-ecoflex`)](https://github.com/althonos/moclo/tree/master/moclo-ecoflex) 38 | 39 | Once installed, kits are available in the `moclo.kits` namespace module. 40 | [Kit-specific documentation](https://moclo.readthedocs.io/en/latest/#kits) is 41 | available as well. 42 | 43 | 44 | ## 🗂️ Registries 45 | 46 | Kit-specific modules and vectors are distributed with the library files, so that 47 | each library provides the base parts needed to create an assembly. They can be 48 | found in the `moclo.registry` namespace. See also the documentation of each 49 | `moclo.registry` submodule for a detail of how sequences were obtained. The 50 | embedded sequences are distributed in GenBank format with the source distributions 51 | of each plugin. 52 | 53 | 54 | ## 🗒️ Notebook 55 | 56 | [![Docker Build Status](https://img.shields.io/docker/build/althonos/moclo.svg?style=flat-square&maxAge=3600)](https://hub.docker.com/r/althonos/moclo/builds/) [![Docker Pulls](https://img.shields.io/docker/pulls/althonos/moclo.svg?style=flat-square&maxAge=3600)](https://hub.docker.com/r/althonos/moclo/) 57 | 58 | This repository provides a YTK-specific Jupyter notebook as a Docker image, 59 | which can be used to generate a protocol for YTK MoClo assembly. Run it locally 60 | using the following command: 61 | ```console 62 | docker run --rm -it -p 8888:8888 althonos/moclo 63 | ``` 64 | and visit [https://localhost:8888/](https://localhost:8888/) to start interacting 65 | with the notebook. 66 | 67 | 68 | ## ⚖️ License 69 | 70 | This project is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). 71 | 72 | *This project is in no way affiliated, sponsored, or otherwise endorsed by [Addgene](https://www.addgene.org) or any of the MoClo toolkit creators. 73 | It was developed by [Martin Larralde](https://github.com/althonos/pyhmmer) 74 | during a placement at the [InBio team](https://research.pasteur.fr/en/team/experimental-and-computational-methods-for-modeling-cellular-processes/) 75 | at the Institut Pasteur of Paris during the summer of 2018.* 76 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from Bio.Seq import Seq 8 | from Bio.SeqRecord import SeqRecord 9 | from Bio.Restriction import BpiI 10 | 11 | from moclo.core import EntryVector, Product 12 | from moclo.record import CircularRecord 13 | 14 | 15 | class TestEntryVector(unittest.TestCase): 16 | class MockEntryVector(EntryVector): 17 | cutter = BpiI 18 | 19 | class MockProduct(Product): 20 | cutter = BpiI 21 | 22 | def test_module_structure(self): 23 | self.assertEqual( 24 | self.MockProduct.structure(), 25 | ( 26 | "GAAGAC" # BpiI 27 | "NN" 28 | "(NNNN)" # Product overhangs (start) 29 | "(NN*N)" # Target 30 | "(NNNN)" # Product overhangs (end) 31 | "NN" 32 | "GTCTTC" # BpiI 33 | ), 34 | ) 35 | 36 | def test_vector_structure(self): 37 | self.assertEqual( 38 | self.MockEntryVector.structure(), 39 | ( 40 | "N" 41 | "(NNNN)" 42 | "(NN" 43 | "GTCTTC" # BpiI 44 | "N*" # Placeholder 45 | "GAAGAC" # BpiI 46 | "NN)" 47 | "(NNNN)" 48 | "N" 49 | ), 50 | ) 51 | 52 | def test_valid(self): 53 | """Assert a valid product is considered valid. 54 | """ 55 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 56 | vector = self.MockEntryVector(SeqRecord(seqv, "vector")) 57 | self.assertTrue(vector.is_valid()) 58 | 59 | def test_valid_rotated(self): 60 | """Assert a valid plasmid is considered valid, even after a rotation. 61 | """ 62 | seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 63 | vector = self.MockEntryVector(CircularRecord(seqv, "vector")) 64 | self.assertTrue(vector.is_valid()) 65 | vector = self.MockEntryVector(vector.record >> 10) 66 | self.assertTrue(vector.is_valid()) 67 | vector = self.MockEntryVector(vector.record >> 10) 68 | self.assertTrue(vector.is_valid()) 69 | 70 | def test_invalid(self): 71 | """Assert an invalid product is considered invalid. 72 | """ 73 | seqv = Seq("ATG") 74 | vector = self.MockEntryVector(CircularRecord(seqv, "vector")) 75 | self.assertFalse(vector.is_valid()) 76 | 77 | # def test_insert_linear(self): 78 | # # Non-circular sequence 79 | # seqp = Seq("TTTTGAAGACTTATGCAAAAAAAACGTATTGTCTTCTTTT") 80 | # product = self.MockProduct(SeqRecord(seqp, "product")) 81 | # seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 82 | # vector = self.MockEntryVector(CircularRecord(seqv, "vector")) 83 | # 84 | # self.assertTrue(product.is_valid()) 85 | # self.assertTrue(vector.is_valid()) 86 | # 87 | # self.assertEqual(vector.placeholder_sequence().seq, 'TTGTCTTCCACAGAAGACTT') 88 | # 89 | # def test_insert_plasmid(self): 90 | # # Sequence with full structure in frame 91 | # seqp = Seq("TTTTGAAGACTTATGCAAAAAAAACGTATTGTCTTCTTTT") 92 | # product = self.MockProduct(CircularRecord(seqp, "product")) 93 | # seqv = Seq("CCATGCTTGTCTTCCACAGAAGACTTCGTAGG") 94 | # vector = self.MockEntryVector(CircularRecord(seqv, "vector")) 95 | # 96 | # self.assertTrue(product.is_valid()) 97 | # self.assertTrue(vector.is_valid()) 98 | # 99 | # self.assertEqual(vector.placeholder_sequence().seq, 'TTGTCTTCCACAGAAGACTT') 100 | # # self.assertEqual(vector.insert(product), 'CCATGCAAAAAAAACGTAGG') 101 | # 102 | # 103 | # def test_insert_plasmid_rotated(self): 104 | # # Sequence needing to be rotated 105 | # seqp = Seq("CAAAAAAAACGTATTGTCTTCTTTTTTTTGAAGACTTATG") 106 | # product = self.MockProduct(CircularRecord(seqp, "product")) 107 | # seqv = Seq("TCGTAGGCCATGCTTGTCTTCCACAGAAGACT") 108 | # vector = self.MockEntryVector(CircularRecord(seqv, "vector")) 109 | # 110 | # self.assertTrue(product.is_valid()) 111 | # self.assertTrue(vector.is_valid()) 112 | # 113 | # self.assertEqual(vector.placeholder_sequence(), 'TTGTCTTCCACAGAAGACTT') 114 | # self.assertEqual(vector.insert(product), 'CCATGCAAAAAAAACGTAGG') # FIXME: DnaRegex 115 | -------------------------------------------------------------------------------- /moclo/moclo/core/_assembly.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import re 6 | import warnings 7 | 8 | import six 9 | from Bio import BiopythonWarning 10 | from Bio.Seq import Seq 11 | from Bio.SeqRecord import SeqRecord 12 | 13 | from .. import errors, __version__ 14 | from ..record import CircularRecord 15 | from .._utils import catch_warnings 16 | 17 | 18 | class AssemblyManager(object): 19 | def __init__(self, vector, modules, id_="assembly", name="assembly"): 20 | 21 | if vector.overhang_start() == vector.overhang_end(): 22 | details = "vector is not suitable for assembly" 23 | raise errors.InvalidSequence(vector, details=details) 24 | 25 | self.vector = vector 26 | self.modules = modules 27 | self.elements = modules + [vector] 28 | self.name = name 29 | self.id = id_ 30 | 31 | @catch_warnings("ignore", category=BiopythonWarning) 32 | def assemble(self): 33 | modmap = self._generate_modules_map() 34 | 35 | for elem in self.elements: 36 | self._deref_citations(elem.record) 37 | 38 | assembly = self._generate_assembly(modmap) 39 | 40 | self._annotate_assembly(assembly) 41 | self._ref_citations(assembly) 42 | for elem in self.elements: 43 | self._ref_citations(elem.record) 44 | 45 | return assembly 46 | 47 | def _generate_modules_map(self): 48 | modmap = {} 49 | for mod in self.modules: 50 | m = modmap.setdefault(mod.overhang_start(), mod) 51 | if m is not mod: 52 | details = "same start overhang: '{}'".format(m.overhang_start()) 53 | raise errors.DuplicateModules(m, mod, details=details) 54 | for overhang in modmap: 55 | m = modmap.get(overhang.reverse_complement()) 56 | if m is not None: 57 | details = "reverse-complementing overhangs: '{}'".format(m.overhang_start()) 58 | raise errors.DuplicateModules(m, modmap[overhang], details=details) 59 | return modmap 60 | 61 | def _generate_assembly(self, modmap): 62 | try: 63 | overhang_next = self.vector.overhang_end() 64 | assembly = SeqRecord(Seq("")) 65 | while overhang_next != self.vector.overhang_start(): 66 | module = modmap.pop(overhang_next) 67 | assembly += module.target_sequence() 68 | overhang_next = module.overhang_end() 69 | except KeyError as ke: 70 | raise six.raise_from(errors.MissingModule(ke.args[0]), None) 71 | if modmap: 72 | warnings.warn(errors.UnusedModules(*modmap.values())) 73 | return CircularRecord(assembly + self.vector.target_sequence()) 74 | 75 | _CITATION_RX = re.compile(r"\[(\d*)\]") 76 | 77 | def _deref_citations(self, record): 78 | references = record.annotations.get("references", []) 79 | for feature in record.features: 80 | for i, ref in enumerate(feature.qualifiers.get("citation", [])): 81 | match = self._CITATION_RX.match(ref) 82 | if match is None: 83 | raise ValueError("invalid citation: '{}'".format(ref)) 84 | ref_index = int(match.group(1)) - 1 85 | feature.qualifiers["citation"][i] = references[ref_index] 86 | 87 | def _ref_citations(self, record): 88 | references = record.annotations.setdefault("references", []) 89 | for feature in record.features: 90 | for i, ref in enumerate(feature.qualifiers.get("citation", [])): 91 | if ref not in references: 92 | references.append(ref) 93 | ref_index = references.find(ref) + 1 94 | feature.qualifiers["citation"][i] = "{}".format(ref_index) 95 | 96 | def _annotate_assembly(self, assembly): 97 | assembly.id = self.id 98 | assembly.name = self.name 99 | ants = assembly.annotations 100 | 101 | ants["topology"] = "circular" 102 | ants["organism"] = ants["source"] = "synthetic DNA construct" 103 | ants["source"] = "synthetic DNA construct" 104 | ants["molecule_type"] = "ds-DNA" 105 | ants["data_file_division"] = "SYN" 106 | ants["comment"] = [ 107 | "Generated with moclo v{}".format(__version__), 108 | "Vector: {}".format(self.vector.record.id), 109 | "Modules: {}".format(", ".join(mod.record.id for mod in self.modules)), 110 | ] 111 | -------------------------------------------------------------------------------- /tests/test_record.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from Bio.Seq import Seq 8 | from Bio.SeqRecord import SeqRecord 9 | from Bio.SeqFeature import SeqFeature, FeatureLocation, ExactPosition 10 | 11 | from moclo.record import CircularRecord 12 | 13 | 14 | class TestCircularRecord(unittest.TestCase): 15 | def test_init(self): 16 | """Assert a `CircularRecord` can be created from a `SeqRecord`. 17 | """ 18 | sr = SeqRecord(seq=Seq("ATGCATGCATGC"), id="test_init") 19 | cr = CircularRecord(sr) 20 | 21 | self.assertIsInstance(cr, CircularRecord) 22 | self.assertNotIsInstance(cr.seq, SeqRecord) 23 | self.assertEqual(cr.seq, sr.seq) 24 | self.assertEqual(cr.id, sr.id) 25 | 26 | def test_linear_error(self): 27 | """Assert a `CircularRecord` does not accept a linear record. 28 | """ 29 | an = {"topology": "linear"} 30 | sr = SeqRecord(seq=Seq("ATGC"), id="linear", annotations=an) 31 | self.assertRaises(ValueError, CircularRecord, sr) 32 | 33 | def test_contains(self): 34 | """Assert `_ in CircularRecord` works as expected. 35 | """ 36 | sr = SeqRecord(seq=Seq("ATGC"), id="test_init") 37 | cr = CircularRecord(sr) 38 | self.assertIn("ATGC", cr) 39 | self.assertIn("GCAT", cr) 40 | self.assertNotIn("ATGCAT", cr) 41 | 42 | def test_shift_features(self): 43 | """Assert a `CircularRecord` shifts its features as intended. 44 | """ 45 | ft = [ 46 | SeqFeature( 47 | FeatureLocation(ExactPosition(0), ExactPosition(2), strand=+1), 48 | type="promoter", 49 | ), 50 | SeqFeature( 51 | FeatureLocation(ExactPosition(2), ExactPosition(4), strand=+1), 52 | type="promoter", 53 | ), 54 | ] 55 | sr = SeqRecord(seq=Seq("ATGC"), id="feats", features=ft) 56 | cr = CircularRecord(sr) 57 | 58 | cr_1 = cr >> 1 59 | self.assertEqual( 60 | cr_1.features[0].location, 61 | FeatureLocation(ExactPosition(1), ExactPosition(3), strand=+1), 62 | ) 63 | self.assertEqual( 64 | cr_1.features[1].location, 65 | FeatureLocation(ExactPosition(3), ExactPosition(5), strand=+1), 66 | ) 67 | 68 | cr_2 = cr_1 >> 1 69 | self.assertEqual( 70 | cr_2.features[0].location, 71 | FeatureLocation(ExactPosition(2), ExactPosition(4), strand=+1), 72 | ) 73 | self.assertEqual( 74 | cr_2.features[1].location, 75 | FeatureLocation(ExactPosition(0), ExactPosition(2), strand=+1), 76 | ) 77 | 78 | cr_3 = cr_2 >> 1 79 | self.assertEqual( 80 | cr_3.features[0].location, 81 | FeatureLocation(ExactPosition(3), ExactPosition(5), strand=+1), 82 | ) 83 | self.assertEqual( 84 | cr_3.features[1].location, 85 | FeatureLocation(ExactPosition(1), ExactPosition(3), strand=+1), 86 | ) 87 | 88 | def test_shift_seq(self): 89 | """Assert a `CircularRecord` shifts its sequence as intended. 90 | """ 91 | cr = CircularRecord(seq=Seq("ATGCATGCATGC"), id="test_shift_seq") 92 | 93 | self.assertEqual((cr >> 2).seq, Seq("GCATGCATGCAT")) 94 | self.assertEqual((cr >> 27).seq, Seq("TGCATGCATGCA")) 95 | self.assertEqual((cr >> len(cr)).seq, cr.seq) 96 | self.assertEqual((cr >> 0).seq, cr.seq) 97 | self.assertEqual((cr >> -1).seq, (cr << 1).seq) 98 | 99 | self.assertEqual((cr << 1).seq, "TGCATGCATGCA") 100 | self.assertEqual((cr << 14).seq, "GCATGCATGCAT") 101 | self.assertEqual((cr << 0).seq, cr.seq) 102 | self.assertEqual((cr << len(cr)).seq, cr.seq) 103 | self.assertEqual((cr << -5).seq, (cr >> 5).seq) 104 | 105 | self.assertEqual((cr << -3).seq, (cr >> 3).seq) 106 | self.assertEqual((cr >> -3).seq, (cr << 3).seq) 107 | 108 | def test_add(self): 109 | """Assert adding to a `CircularRecord` raises a type error. 110 | """ 111 | cr = CircularRecord(seq=Seq("ATGCATGCATGC"), id="test_shift_seq") 112 | with self.assertRaises(TypeError): 113 | cr + cr 114 | 115 | def test_radd(self): 116 | """Assert right-adding to a `CircularRecord` raises a type error. 117 | """ 118 | cr = CircularRecord(seq=Seq("ATGCATGCATGC"), id="test_shift_seq") 119 | with self.assertRaises(TypeError): 120 | cr += cr 121 | -------------------------------------------------------------------------------- /moclo/CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog `_ and this 7 | project adheres to `Semantic Versioning `_. 8 | 9 | Unreleased_ 10 | ----------- 11 | 12 | v0.4.7_ - 2021-11-08 13 | -------------------- 14 | 15 | Changed 16 | ''''''' 17 | - Dropped support for Python versions older than Python 3.6. 18 | - Required minimum version of ``1.78`` for Biopython. 19 | 20 | 21 | v0.4.6_ - 2019-07-25 22 | ------------------- 23 | 24 | Changed 25 | ''''''' 26 | - Switch from ``cached-property`` to ``property-cached`` in dependencies. 27 | 28 | v0.4.5_ - 2019-02-22 29 | -------------------- 30 | 31 | Fixed 32 | ''''' 33 | - Support all ``fs`` versions under `3.0`. 34 | 35 | v0.4.4_ - 2019-02-11 36 | -------------------- 37 | 38 | Changed 39 | ''''''' 40 | - Add ``2.3.0`` to the supported ``fs`` versions. 41 | 42 | v0.4.3_ - 2019-01-06 43 | -------------------- 44 | 45 | Changed 46 | ''''''' 47 | - Add ``2.2.0`` to the supported ``fs`` versions. 48 | 49 | Added 50 | ''''' 51 | - Add ``Item.record`` shortcut to ``Item.entity.record`` in ``moclo.registry``. 52 | - Make ``moclo.core`` abstract classes check for illegal sites in sequence to 53 | be identified as *valid*. 54 | - This *CHANGELOG* file. 55 | 56 | Documented 57 | '''''''''' 58 | - Fix typos. 59 | 60 | 61 | v0.4.2_ - 2018-08-16 62 | -------------------- 63 | 64 | Fixed 65 | ''''' 66 | - Some registries not loading ``CircularRecord`` instances. 67 | 68 | 69 | v0.4.1_ - 2018-08-16 70 | -------------------- 71 | 72 | Changed 73 | ''''''' 74 | - Bump required ``fs`` version to ``2.1.0``. 75 | 76 | 77 | v0.4.0_ - 2018-08-10 78 | -------------------- 79 | 80 | Added 81 | ''''' 82 | - ``AbstractPart.characterize`` to load a record into a part instance. 83 | - Option to include / exclude ``ELabFTWRegistry`` items using tags. 84 | 85 | 86 | v0.3.0_ - 2018-08-07 87 | -------------------- 88 | 89 | Added 90 | ''''' 91 | - Annotate assembled vectors as *circular* in ``AbstractVector.assemble``. 92 | - *eLabFTW* registry connector in ``moclo.registry.elabftw``. 93 | 94 | Changed 95 | ''''''' 96 | - Move ``Item._find_type`` to public function ``moclo.registry.utils.find_type``. 97 | - Improve annotation generated in ``AbstractVector.assemble``. 98 | 99 | Fixed 100 | ''''' 101 | - ``AbstractPart`` subclasses not being recognized as abstract. 102 | 103 | 104 | v0.2.1_ - 2018-07-27 105 | -------------------- 106 | 107 | Added 108 | ''''' 109 | - ``moclo.registry.utils`` module with resistance idenfication function. 110 | - Make ``AbstractVector.assemble`` add an alphabet to the generated sequence. 111 | 112 | Documented 113 | '''''''''' 114 | - Improved ``README.rst`` file. 115 | 116 | 117 | v0.2.0_ - 2018-07-24 118 | -------------------- 119 | 120 | Added 121 | ''''' 122 | - Use ``AbstracModule.cutter`` and ``AbstractVector.cutter`` to deduce the 123 | required structure for modules and vectors. 124 | - ``AbstractPart`` class to generate sequence structure based on part signature. 125 | - Add registry API in ``moclo.registry`` module. 126 | 127 | Changed 128 | ''''''' 129 | - Make ``StructuredRecord`` convert ``SeqRecord`` to ``CircularRecord`` on 130 | instantiation if needed. 131 | - Use ``target_sequence`` method in ``AbstractVector.assemble``. 132 | - Make modules and vectors add sources to their target sequences when assembled. 133 | - Patch ``CircularRecord.reverse_complement`` to return a ``CircularRecord``. 134 | 135 | 136 | Documented 137 | '''''''''' 138 | - Add ``moclo.base.parts`` to documentation. 139 | - Add example in ``AbstractPart`` docstring. 140 | - Fix documentation of ``moclo.base`` 141 | 142 | Fixed 143 | ''''' 144 | - Fix ``AbstracModule.target_sequence`` and ``AbstractVector.target_sequence`` to 145 | take into account cutter overhand position. 146 | 147 | 148 | v0.1.0_ - 2018-07-12 149 | -------------------- 150 | 151 | Initial public release. 152 | 153 | .. _Unreleased: https://github.com/althonos/moclo/compare/v0.4.7...HEAD 154 | .. _v0.4.6: https://github.com/althonos/moclo/compare/v0.4.6...v0.4.7 155 | .. _v0.4.6: https://github.com/althonos/moclo/compare/v0.4.5...v0.4.6 156 | .. _v0.4.5: https://github.com/althonos/moclo/compare/v0.4.4...v0.4.5 157 | .. _v0.4.4: https://github.com/althonos/moclo/compare/v0.4.3...v0.4.4 158 | .. _v0.4.3: https://github.com/althonos/moclo/compare/v0.4.2...v0.4.3 159 | .. _v0.4.2: https://github.com/althonos/moclo/compare/v0.4.1...v0.4.2 160 | .. _v0.4.1: https://github.com/althonos/moclo/compare/v0.4.0...v0.4.1 161 | .. _v0.4.0: https://github.com/althonos/moclo/compare/v0.3.0...v0.4.0 162 | .. _v0.3.0: https://github.com/althonos/moclo/compare/v0.2.1...v0.3.0 163 | .. _v0.2.1: https://github.com/althonos/moclo/compare/v0.2.0...v0.2.1 164 | .. _v0.2.0: https://github.com/althonos/moclo/compare/v0.1.0...v0.2.0 165 | .. _v0.1.0: https://github.com/althonos/moclo/compare/20aa50fb2202279215c36e2b687a7e989667e34f...v0.1.0 166 | -------------------------------------------------------------------------------- /tests/test_cidar.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | from moclo.kits import cidar 8 | from moclo.registry.cidar import CIDARRegistry 9 | 10 | from ._utils import PartsMetaCase, AssemblyTestCase, build_registries, expectFailure 11 | 12 | 13 | # --- Test Suite Metaclass --------------------------------------------------- 14 | 15 | _Meta = PartsMetaCase("CIDAR", CIDARRegistry, __name__) 16 | 17 | 18 | def exclude_dva(item): 19 | return item.id.startswith("DVA_") 20 | 21 | 22 | def exclude_dvk(item): 23 | return item.id.startswith("DVK_") 24 | 25 | # --- Test CIDAR Parts ------------------------------------------------------- 26 | 27 | 28 | TestCIDARPromoter = _Meta(cidar.CIDARPromoter, "Promoter", exclude_dva) 29 | TestCIDARibosomeBindingSite = _Meta(cidar.CIDARRibosomeBindingSite, "RBS", exclude_dva) 30 | TestCIDARCodingSequence = _Meta(cidar.CIDARCodingSequence, "CDS", exclude_dva) 31 | TestCIDARTerminator = _Meta(cidar.CIDARTerminator, "Terminator", exclude_dva) 32 | 33 | # Patch expected failures: 34 | # - R0063_AB contains 3 BsaI sites instead of 2 35 | expectFailure(TestCIDARPromoter, "test_R0063_AB_is_Promoter") 36 | 37 | # --- Test CIDAR vectors ----------------------------------------------------- 38 | 39 | TestCIDAREntryVector = _Meta(cidar.CIDAREntryVector, "EntryVector", exclude_dvk) 40 | TestCIDARCassetteVector = _Meta(cidar.CIDARCassetteVector, "CassetteVector", exclude_dva) 41 | 42 | # --- Test CIDAR Assemblies -------------------------------------------------- 43 | 44 | # Generate test cases based on test assemblies 45 | 46 | 47 | class TestCIDARAssembly(AssemblyTestCase): 48 | @classmethod 49 | def setUpClass(cls): 50 | build_registries("cidar") 51 | cls.registry = CIDARRegistry() 52 | 53 | def test_pJ02B2Rm_EF(self): 54 | expected = self.registry["pJ02B2Rm_EF"].entity.record 55 | vector = self.registry["DVK_EF"].entity 56 | mods = [ 57 | self.registry[x].entity 58 | for x in ("J23102_EB", "BCD2_BC", "E1010m_CD", "B0015_DF") 59 | ] 60 | self.assertAssembly(vector, mods, expected) 61 | 62 | def test_pJ02B2Rm_AE(self): 63 | expected = self.registry["pJ02B2Rm_AE"].entity.record 64 | vector = self.registry["DVK_AE"].entity 65 | mods = [ 66 | self.registry[x].entity 67 | for x in ("J23102_AB", "BCD2_BC", "E1010m_CD", "B0015_DE") 68 | ] 69 | self.assertAssembly(vector, mods, expected) 70 | 71 | def test_pJ02B2RmA_EF(self): 72 | expected = self.registry["pJ02B2Rm_EF(A)"].entity.record 73 | vector = self.registry["DVA_EF"].entity 74 | mods = [ 75 | self.registry[x].entity 76 | for x in ("J23102_EB", "BCD2_BC", "E1010m_CD", "B0015_DF") 77 | ] 78 | self.assertAssembly(vector, mods, expected) 79 | 80 | def test_pJ02B2RmA_AE(self): 81 | expected = self.registry["pJ02B2Rm_AE(A)"].entity.record 82 | vector = self.registry["DVA_AE"].entity 83 | mods = [ 84 | self.registry[x].entity 85 | for x in ("J23102_AB", "BCD2_BC", "E1010m_CD", "B0015_DE") 86 | ] 87 | self.assertAssembly(vector, mods, expected) 88 | 89 | # ERRORS BELOW CAUSED BY NON-CONSISTENT GFP SEQUENCE BETWEEN: 90 | # - E0040 iGEM part sequence 91 | # - E0040m plasmid 92 | # - pJ02B2Gm* plasmid 93 | 94 | @unittest.expectedFailure 95 | def test_pJ02B2Gm_AE(self): 96 | expected = self.registry["pJ02B2Gm_AE"].entity.record 97 | vector = self.registry["DVK_AE"].entity 98 | mods = [ 99 | self.registry[x].entity 100 | for x in ("J23102_AB", "BCD2_BC", "E0040m_CD", "B0015_DE") 101 | ] 102 | self.assertAssembly(vector, mods, expected) 103 | 104 | @unittest.expectedFailure 105 | def test_pJ02B2Gm_EF(self): 106 | expected = self.registry["pJ02B2Gm_EF"].entity.record 107 | vector = self.registry["DVK_EF"].entity 108 | mods = [ 109 | self.registry[x].entity 110 | for x in ("J23102_EB", "BCD2_BC", "E0040m_CD", "B0015_DF") 111 | ] 112 | self.assertAssembly(vector, mods, expected) 113 | 114 | @unittest.expectedFailure 115 | def test_pJ02B2RmGm_AF(self): 116 | expected = self.registry["pJ02B2Rm:Gm_AF"].entity.record 117 | vector = self.registry["DVA_AF"].entity 118 | mods = [self.registry[x].entity for x in ("pJ02B2Rm_AE", "pJ02B2Gm_EF")] 119 | from Bio.SeqIO import write 120 | 121 | write(expected, "/tmp/expected.gb", "gb") 122 | actual = vector.assemble(*mods) 123 | bbp = next( 124 | f 125 | for f in actual.features 126 | if "BioBrick prefix" in f.qualifiers.get("label", []) 127 | ) 128 | write(actual << bbp.location.start - 1, "/tmp/actual.gb", "gb") 129 | self.assertAssembly(vector, mods, expected) 130 | -------------------------------------------------------------------------------- /tests/_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import os 6 | import unittest 7 | import subprocess 8 | import sys 9 | 10 | import Bio.SeqIO 11 | import contexter 12 | import fs.path 13 | import fs.archive.tarfs 14 | 15 | from moclo.record import CircularRecord 16 | 17 | # --- Constants -------------------------------------------------------------- 18 | 19 | # fs.osfs.OSFS: FS located at the root of the project 20 | PROJFS = fs.open_fs(os.path.join(__file__, "..", "..")) 21 | 22 | # fs.osfs.OSFS: FS where test data is located 23 | DATAFS = PROJFS.opendir("tests/data") 24 | 25 | 26 | # --- Setup Helper ----------------------------------------------------------- 27 | 28 | class Registries(object): 29 | def __init__(self): 30 | self._built = set() 31 | 32 | def __call__(self, name): 33 | if name not in self._built: 34 | subprocess.Popen( 35 | args=[sys.executable, "setup.py", "build_ext", "-i"], 36 | cwd=PROJFS.getsyspath("moclo-{}".format(name)), 37 | stdout=subprocess.PIPE, 38 | stderr=subprocess.PIPE, 39 | ).communicate() 40 | self._built.add(name) 41 | 42 | 43 | build_registries = Registries() 44 | 45 | 46 | # --- Tests helpers ---------------------------------------------------------- 47 | 48 | def expectFailure(cls, method): 49 | setattr(cls, method, unittest.expectedFailure(getattr(cls, method))) 50 | 51 | 52 | class PartsMetaCase(object): 53 | def __init__(self, kit_name, registry_factory, module): 54 | build_registries(kit_name.lower()) 55 | self.kit_name = kit_name 56 | self.registry = registry_factory() 57 | self.module = module 58 | 59 | def __call__(self, part_cls, part_name, exclude=lambda item: False): 60 | """Create a whole test case for the given class. 61 | """ 62 | tests = ( 63 | self.make_test(item, part_cls=part_cls, part_name=part_name) 64 | for item in self.registry.values() 65 | if not exclude(item) 66 | ) 67 | attrs = {test.__name__: test for test in tests} 68 | attrs["__name__"] = name = str("Test{}".format(part_cls.__name__)) 69 | attrs["__module__"] = self.module 70 | return type(name, (unittest.TestCase,), attrs) 71 | 72 | def make_test(self, item, part_cls, part_name): 73 | """Create a single test for the given item. 74 | 75 | If ``ìtem.entity`` is a ``part_cls`` instance, it will check that 76 | ``part_cls(item.entity.record).is_valid()`` is `True` (`False` 77 | otherwise), i.e. it checks that the record is only identified as 78 | the actual type it should be. 79 | """ 80 | rec = item.entity.record 81 | if isinstance(item.entity, part_cls): 82 | 83 | def test(self_): 84 | err = "{} is not a valid {} {} but should be!" 85 | self_.assertTrue( 86 | part_cls(rec).is_valid(), 87 | err.format(item.id, self.kit_name, part_name), 88 | ) 89 | 90 | name = "test_{}_is_{}" 91 | doc = "Check that {} ({}) is a {} {}.\n" 92 | else: 93 | 94 | def test(self_): 95 | err = "{} is a valid {} {} but should not be!" 96 | self_.assertFalse( 97 | part_cls(rec).is_valid(), 98 | err.format(item.id, self.kit_name, part_name), 99 | ) 100 | 101 | name = "test_{}_is_not_{}" 102 | doc = "Check that {} ({}) is not a {} {}.\n" 103 | test.__name__ = str(name.format(item.id, part_name)) 104 | test.__doc__ = doc.format( 105 | item.id, item.entity.record.name, self.kit_name, part_name 106 | ) 107 | return test 108 | 109 | 110 | class AssemblyTestCase(unittest.TestCase): 111 | def assertAssembly(self, vector, modules, result): 112 | assembly = vector.assemble(*modules) 113 | self.assertEqual(len(assembly), len(result), "lengths differ") 114 | self.assertIn(assembly.seq, result.seq + result.seq, "sequences differ") 115 | 116 | def load_data(self, name): 117 | archive_path = "cases/{}.tar.xz".format(name) 118 | 119 | if not DATAFS.exists(archive_path): 120 | raise unittest.SkipTest("no test case found") 121 | 122 | with contexter.Contexter() as ctx: 123 | # open FASTA files 124 | casefs = ctx << fs.archive.open_archive(DATAFS, archive_path) 125 | result_fa = ctx << casefs.open("result.fa") 126 | vector_fa = ctx << casefs.open("vector.fa") 127 | modules_fa = ctx << casefs.open("modules.fa") 128 | # load records from FASTA handles 129 | res = CircularRecord(Bio.SeqIO.read(result_fa, "fasta")) 130 | vec = CircularRecord(Bio.SeqIO.read(vector_fa, "fasta")) 131 | mods = { 132 | record.id: CircularRecord(record) 133 | for record in Bio.SeqIO.parse(modules_fa, "fasta") 134 | } 135 | return res, vec, mods 136 | -------------------------------------------------------------------------------- /tests/test_registry.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import unittest 6 | 7 | import fs 8 | import six 9 | from Bio.SeqIO import write 10 | 11 | from moclo.kits import ytk, cidar 12 | from moclo.record import CircularRecord 13 | from moclo.registry import base 14 | from moclo.registry.ytk import YTKRegistry, PTKRegistry 15 | from moclo.registry.cidar import CIDARRegistry 16 | 17 | from ._utils import build_registries 18 | 19 | 20 | class TestEmbeddedRegistry(unittest.TestCase): 21 | @classmethod 22 | def setUpClass(cls): 23 | build_registries("ytk") 24 | build_registries("cidar") 25 | 26 | def test_circular_record(self): 27 | r = YTKRegistry() 28 | self.assertIsInstance(r["pYTK002"].entity.record, CircularRecord) 29 | 30 | def test_ytk_registry(self): 31 | r = YTKRegistry() 32 | 33 | # typecheck 34 | self.assertIsInstance(r["pYTK002"].entity, ytk.YTKPart1) 35 | self.assertIsInstance(r["pYTK047"].entity, ytk.YTKPart234r) 36 | self.assertIsInstance(r["pYTK096"].entity, ytk.YTKCassetteVector) 37 | 38 | # resistance check 39 | self.assertEqual(r["pYTK005"].resistance, "Chloramphenicol") 40 | self.assertEqual(r["pYTK085"].resistance, "Spectinomycin") 41 | 42 | # missing key check 43 | self.assertRaises(KeyError, r.__getitem__, "pYTK200") 44 | 45 | def test_ptk_registry(self): 46 | r = PTKRegistry() 47 | 48 | # typecheck 49 | self.assertIsInstance(r["pPTK004"].entity, ytk.YTKPart2) 50 | self.assertIsInstance(r["pPTK019"].entity, ytk.YTKPart4) 51 | self.assertIsInstance(r["pPTK013"].entity, ytk.YTKPart3a) 52 | 53 | # resistance check 54 | self.assertEqual(r["pPTK005"].resistance, "Chloramphenicol") 55 | 56 | def test_cidar_registry(self): 57 | r = CIDARRegistry() 58 | 59 | # typecheck 60 | self.assertIsInstance(r["C0062_CD"].entity, cidar.CIDARCodingSequence) 61 | self.assertIsInstance(r["BCD8_BC"].entity, cidar.CIDARRibosomeBindingSite) 62 | self.assertIsInstance(r["DVA_GB"].entity, cidar.CIDAREntryVector) 63 | self.assertIsInstance(r["DVK_GH"].entity, cidar.CIDARCassetteVector) 64 | 65 | # resistance check 66 | self.assertEqual(r["DVA_BC"].resistance, "Ampicillin") 67 | self.assertEqual(r["DVK_EF"].resistance, "Kanamycin") 68 | 69 | 70 | class TestFilesystemRegistry(unittest.TestCase): 71 | @classmethod 72 | def setUpClass(cls): 73 | build_registries("ytk") 74 | r = YTKRegistry() 75 | 76 | cls.memfs = fs.open_fs("mem://") 77 | 78 | buff = six.StringIO() 79 | write([r["pYTK002"].entity.record], buff, "genbank") 80 | with cls.memfs.open("pYTK002.gb", "w") as f: 81 | f.write(buff.getvalue()) 82 | 83 | buff = six.StringIO() 84 | write([r["pYTK038"].entity.record], buff, "genbank") 85 | with cls.memfs.open("pYTK038.gb", "w") as f: 86 | f.write(buff.getvalue()) 87 | 88 | @classmethod 89 | def tearDownClass(cls): 90 | cls.memfs.close() 91 | 92 | def test_mem_registry(self): 93 | r = base.FilesystemRegistry(self.memfs, ytk.YTKPart) 94 | self.assertIn("pYTK002", r) 95 | self.assertNotIn("pYTK003", r) 96 | self.assertIsInstance(r["pYTK002"].entity, ytk.YTKPart1) 97 | self.assertIsInstance(r["pYTK038"].entity, ytk.YTKPart3a) 98 | self.assertEqual(r["pYTK002"].resistance, "Chloramphenicol") 99 | 100 | def test_invalid_base(self): 101 | r = base.FilesystemRegistry(self.memfs, ytk.YTKPart8) 102 | self.assertRaises(RuntimeError, r.__getitem__, "pYTK002") 103 | 104 | def test_len(self): 105 | r = base.FilesystemRegistry(self.memfs, ytk.YTKPart) 106 | self.assertEqual(len(r), len(list(r))) 107 | self.assertEqual(len(r), len(self.memfs.listdir("/"))) 108 | 109 | def test_iter(self): 110 | r = base.FilesystemRegistry(self.memfs, ytk.YTKPart) 111 | self.assertTrue(all(x in r for x in r)) 112 | self.assertEqual(sorted(r), ["pYTK002", "pYTK038"]) 113 | 114 | def test_circular_record(self): 115 | r = base.FilesystemRegistry(self.memfs, ytk.YTKPart) 116 | self.assertIsInstance(r["pYTK002"].entity.record, CircularRecord) 117 | 118 | 119 | class TestCombinedRegistry(unittest.TestCase): 120 | @classmethod 121 | def setUpClass(cls): 122 | build_registries("ytk") 123 | cls.ptk = PTKRegistry() 124 | cls.ytk = YTKRegistry() 125 | cls.registry = base.CombinedRegistry() 126 | cls.registry << cls.ytk << cls.ptk 127 | 128 | def test_contains(self): 129 | self.assertIn("pYTK001", self.registry) 130 | self.assertIn("pPTK001", self.registry) 131 | self.assertNotIn("B0030_AF", self.registry) 132 | 133 | def test_length(self): 134 | self.assertEqual(len(self.registry), len(YTKRegistry()) + len(PTKRegistry())) 135 | 136 | def test_getitem(self): 137 | self.assertIs(self.registry["pYTK001"], self.ytk["pYTK001"]) 138 | self.assertIs(self.registry["pPTK003"], self.ptk["pPTK003"]) 139 | self.assertRaises(KeyError, self.registry.__getitem__, "B0030_AF") 140 | 141 | def test_iter(self): 142 | self.assertEqual(sorted(self.registry), sorted(self.ptk) + sorted(self.ytk)) 143 | -------------------------------------------------------------------------------- /moclo/moclo/core/modules.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Moclo module classes. 3 | 4 | A module is a sequence of DNA that contains a sequence of interest, such as a 5 | promoter, a CDS, a protein binding site, etc., organised in a way it can be 6 | combined to other modules to create an assembly. This involves flanking that 7 | target sequence with Type IIS restriction sites, which depend on the level of 8 | the module, as well as the chosen MoClo protocol. 9 | """ 10 | 11 | import typing 12 | 13 | from Bio.Seq import Seq 14 | from property_cached import cached_property 15 | 16 | from .. import errors 17 | from ._structured import StructuredRecord 18 | from ._utils import cutter_check, add_as_source 19 | 20 | if typing.TYPE_CHECKING: 21 | from typing import Union, Text # noqa: F401 22 | from Bio.SeqRecord import SeqRecord # noqa: F401 23 | 24 | 25 | class AbstractModule(StructuredRecord): 26 | """An abstract modular cloning module. 27 | 28 | Attributes: 29 | cutter (`~Bio.Restriction.Restriction.RestrictionType`): the enzyme 30 | used to cut the target sequence from the backbone plasmid during 31 | Golden Gate assembly. 32 | 33 | """ 34 | 35 | _level = None # type: Union[None, int] 36 | cutter = NotImplemented # type: Union[NotImplemented, RestrictionType] 37 | 38 | def __new__(cls, *args, **kwargs): 39 | cutter_check(cls.cutter, name=cls.__name__) 40 | return super(AbstractModule, cls).__new__(cls) 41 | 42 | @classmethod 43 | def structure(cls): 44 | # type: () -> Text 45 | """Get the module structure, as a DNA regex pattern. 46 | 47 | Warning: 48 | If overloading this method, the returned pattern must include 3 49 | capture groups to capture the following features: 50 | 51 | 1. The upstream (5') overhang sequence 52 | 2. The module target sequence 53 | 3. The downstream (3') overhang sequence 54 | 55 | """ 56 | upstream = cls.cutter.elucidate() 57 | downstream = str(Seq(upstream).reverse_complement()) 58 | return "".join( 59 | [ 60 | upstream.replace("^", "(").replace("_", ")("), 61 | "N*", 62 | downstream.replace("^", ")").replace("_", ")("), 63 | ] 64 | ) 65 | 66 | def overhang_start(self): 67 | # type: () -> Seq 68 | """Get the upstream overhang of the target sequence. 69 | 70 | Returns: 71 | `~Bio.Seq.Seq`: the downstream overhang. 72 | 73 | """ 74 | return self._match.group(1).seq 75 | 76 | def overhang_end(self): 77 | # type: () -> Seq 78 | """Get the downstream overhang of the target sequence. 79 | 80 | Returns: 81 | `~Bio.Seq.Seq`: the downstream overhang. 82 | 83 | """ 84 | return self._match.group(3).seq 85 | 86 | def target_sequence(self): 87 | # type: () -> SeqRecord 88 | """Get the target sequence of the module. 89 | 90 | Modules are often stored in a standardized way, and contain more than 91 | the sequence of interest: for instance they can contain an antibiotic 92 | marker, that will not be part of the assembly when that module is 93 | assembled into a vector; only the target sequence is inserted. 94 | 95 | Returns: 96 | `~Bio.SeqRecord.SeqRecord`: the target sequence with annotations. 97 | 98 | Note: 99 | Depending on the cutting direction of the restriction enzyme used 100 | during assembly, the overhang will be left at the beginning or at 101 | the end, so the obtained record is exactly the sequence the enzyme 102 | created during restriction. 103 | 104 | """ 105 | if self.cutter.is_3overhang(): 106 | start, end = self._match.span(2)[0], self._match.span(3)[1] 107 | else: 108 | start, end = self._match.span(1)[0], self._match.span(2)[1] 109 | return add_as_source(self.record, (self.record << start)[: end - start]) 110 | 111 | @cached_property 112 | def _match(self): 113 | _match = super(AbstractModule, self)._match 114 | if len(self.cutter.catalyse(_match.group(0).seq)) > 3: 115 | raise errors.IllegalSite(self.seq) 116 | return _match 117 | 118 | 119 | class Product(AbstractModule): 120 | """A level -1 module, often obtained as a PCR product. 121 | 122 | Modules of this level are the lowest components of the MoClo system, but 123 | are not practical to work with until they are assembled in a standard 124 | vector to obtain *entries*. 125 | 126 | """ 127 | 128 | _level = -1 129 | 130 | 131 | class Entry(AbstractModule): 132 | """A level 0 module, often obtained from the official toolkits plamisds. 133 | 134 | Entries are assembled from products into a standard vector suitable for 135 | selection and storage. 136 | 137 | """ 138 | 139 | _level = 0 140 | 141 | 142 | class Cassette(AbstractModule): 143 | """A level 1 module, also refered as a Transcriptional Unit. 144 | 145 | Cassettes can either express genes in their target organism, or be 146 | assembled into *multigene* modules for expressing many genes at once, 147 | depending on the chosen cassette vector during level 0 assembly. 148 | 149 | """ 150 | 151 | _level = 1 152 | 153 | 154 | class Device(AbstractModule): 155 | """A level 2 module, also refered as a Multigene plasmid. 156 | 157 | Modules of this level are assembled from several transcriptional units so 158 | that they contain several genes that can be expressed all at once. Most of 159 | the MoClo implementations are designed so that multiple devices can be 160 | assembled into a module that is also a valid level 1 module, as does the 161 | **Golden Braid** system with its **α** and **Ω** plasmids. 162 | 163 | """ 164 | 165 | _level = 2 166 | -------------------------------------------------------------------------------- /moclo/moclo/registry/base.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import abc 6 | import io 7 | import typing 8 | import tarfile 9 | 10 | import Bio.SeqIO 11 | import fs 12 | import pkg_resources 13 | import six 14 | from fs.wrap import read_only 15 | from fs.path import splitext 16 | from property_cached import cached_property 17 | 18 | from .._impl import bz2, json 19 | from ..record import CircularRecord 20 | from ..core import AbstractModule, AbstractVector, AbstractPart 21 | from ._utils import find_resistance 22 | 23 | 24 | class Item( 25 | typing.NamedTuple( 26 | "Item", 27 | [ 28 | ("id", typing.Text), 29 | ("name", typing.Text), 30 | ("entity", typing.Union[AbstractModule, AbstractVector]), 31 | ("resistance", typing.Text), 32 | ], 33 | ) 34 | ): 35 | """A uniquely identified record in a registry. 36 | """ 37 | 38 | @property 39 | def record(self): 40 | return self.entity.record 41 | 42 | 43 | class AbstractRegistry(typing.Mapping[typing.Text, Item]): 44 | """An abstract registry holding MoClo plasmids. 45 | """ 46 | 47 | 48 | class CombinedRegistry(AbstractRegistry): 49 | """A registry combining several registries into a single collection. 50 | """ 51 | 52 | def __init__(self): 53 | self._data = {} 54 | 55 | def __lshift__(self, registry): 56 | self.add_registry(registry) 57 | return self 58 | 59 | def add_registry(self, registry): 60 | for item in six.itervalues(registry): 61 | self._data.setdefault(item.id, item) 62 | 63 | def __getitem__(self, item): 64 | return self._data[item] 65 | 66 | def __contains__(self, item): 67 | return item in self._data 68 | 69 | def __iter__(self): 70 | return iter(self._data) 71 | 72 | def __len__(self): 73 | return len(self._data) 74 | 75 | 76 | class EmbeddedRegistry(AbstractRegistry): 77 | """An embedded registry, distributed with the library source code. 78 | 79 | Records are stored within a GZip compressed Tar archive, using standard 80 | annotations to allow retrieving features easily. 81 | """ 82 | 83 | _module = NotImplemented 84 | _file = NotImplemented 85 | _types = NotImplemented 86 | 87 | def __hash__(self): 88 | return hash((EmbeddedRegistry, self._file)) 89 | 90 | def __eq__(self, other): 91 | if isinstance(other, EmbeddedRegistry): 92 | return self._file == other._file 93 | return False 94 | 95 | def _load_name(self, record): 96 | return record.name 97 | 98 | def _load_resistance(self, record): 99 | try: 100 | return find_resistance(record) 101 | except RuntimeError: 102 | msg = "could not find antibiotics resistance of '{}'" 103 | six.raise_from(RuntimeError(msg.format(record.id)), None) 104 | 105 | @abc.abstractmethod 106 | def _load_entity(self, record): 107 | return NotImplemented 108 | 109 | @cached_property 110 | def _data(self): 111 | data = {} 112 | with pkg_resources.resource_stream(self._module, self._file) as rs: 113 | with tarfile.open(mode="r:gz", fileobj=rs) as tar: 114 | for entry in iter(tar.next, None): 115 | fileobj = io.TextIOWrapper(tar.extractfile(entry)) 116 | record = CircularRecord(Bio.SeqIO.read(fileobj, "gb")) 117 | data[record.id] = Item( 118 | id=record.id, 119 | name=self._load_name(record), 120 | resistance=self._load_resistance(record), 121 | entity=self._load_entity(record), 122 | ) 123 | return data 124 | 125 | def __len__(self): 126 | with pkg_resources.resource_stream(self._module, self._file) as rs: 127 | with tarfile.open(fileobj=rs) as tar: 128 | return len(tar.getmembers()) 129 | 130 | def __getitem__(self, item): 131 | return self._data[item] 132 | 133 | def __iter__(self): 134 | with pkg_resources.resource_stream(self._module, self._file) as rs: 135 | with tarfile.open(mode="r:gz", fileobj=rs) as tar: 136 | yield from (entry.name for entry in iter(tar.next, None)) 137 | 138 | 139 | class FilesystemRegistry(AbstractRegistry): 140 | """A registry located on a filesystem. 141 | """ 142 | 143 | def __init__(self, fs_url, base, extensions=("gb", "gbk")): 144 | 145 | bases = (AbstractPart, AbstractModule, AbstractVector) 146 | if not isinstance(base, type) or not issubclass(base, (bases)): 147 | raise TypeError("base cannot be '{}'".format(base)) 148 | 149 | self.fs = read_only(fs.open_fs(fs_url)) 150 | self.base = base 151 | self._recurse = False 152 | self._extensions = extensions 153 | 154 | @property 155 | def _files(self): 156 | return ["*.{}".format(extension) for extension in self._extensions] 157 | 158 | def __iter__(self): 159 | for f in self.fs.filterdir("/", files=self._files, exclude_dirs=["*"]): 160 | name, _ = splitext(f.name) 161 | yield name 162 | 163 | def __len__(self): 164 | return sum( 165 | 1 for _ in self.fs.filterdir("/", files=self._files, exclude_dirs=["*"]) 166 | ) 167 | 168 | def __getitem__(self, item): 169 | files = ("{}.{}".format(item, extension) for extension in self._extensions) 170 | for name in files: 171 | if self.fs.isfile(name): 172 | with self.fs.open(name) as handle: 173 | record = CircularRecord(Bio.SeqIO.read(handle, "genbank")) 174 | record.id, _ = splitext(name) 175 | return Item( 176 | id=record.id, 177 | name=record.description, 178 | entity=self.base.characterize(record), 179 | resistance=find_resistance(record), 180 | ) 181 | raise KeyError(item) 182 | -------------------------------------------------------------------------------- /moclo-ecoflex/moclo/kits/ecoflex.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """An implementation of the EcoFlex ToolKit for the Python MoClo library. 3 | 4 | References: 5 | 1. `Moore, S. J., Lai, H.-E., Kelwick, R. J. R., Chee, S. M., Bell, D. J., 6 | Polizzi, K. M., Freemont, P. S. (2016). 7 | EcoFlex: A Multifunctional MoClo Kit for E. coli Synthetic Biology. 8 | ACS Synthetic Biology, 5(10), 1059–1069. 9 | `_ 10 | 2. `Weber, E., Engler, C., Gruetzner, R., Werner, S., Marillonnet, S. (2011). 11 | A Modular Cloning System for Standardized Assembly of Multigene Constructs. 12 | PLOS ONE, 6(2), e16765. 13 | `_ 14 | 15 | """ 16 | 17 | import six 18 | from Bio.Restriction import BsmBI, BsaI 19 | 20 | from ..core import parts, modules, vectors 21 | 22 | __author__ = "Martin Larralde " 23 | __version__ = ( 24 | __import__("pkg_resources") 25 | .resource_string(__name__, "ecoflex.version") 26 | .strip() 27 | .decode("ascii") 28 | ) 29 | 30 | 31 | ### VECTORS 32 | 33 | # TODO 34 | # class EcoFlexEntryVector(vectors.EntryVector): 35 | # """An EcoFlex MoClo entry vector. 36 | # """ 37 | # 38 | # cutter = NotImplemented 39 | 40 | 41 | class EcoFlexCassetteVector(vectors.CassetteVector): 42 | """An EcoFlex MoClo cassette vector. 43 | """ 44 | 45 | cutter = BsaI 46 | 47 | @staticmethod 48 | def structure(): # noqa: D105 49 | return ( 50 | "CGTCTC" # BsmBI 51 | "N" 52 | "NNNN" # Cassette overhang (start) 53 | "(NNNN)" # Vector overhang (start) 54 | "(N" 55 | "GAGACC" # BsaI 56 | "N*?" # Placeholder sequence 57 | "GGTCTC" # BsaI 58 | "N)" 59 | "(NNNN)" # Vector overhang (end) 60 | "NNNN" # Cassette overhang (end) 61 | "N" 62 | "GAGACG" # BsmBI 63 | ) 64 | 65 | 66 | class EcoFlexDeviceVector(vectors.DeviceVector): 67 | """An EcoFlex MoClo device vector. 68 | """ 69 | 70 | cutter = BsmBI 71 | 72 | @staticmethod 73 | def structure(): # noqa: D105 74 | return ( 75 | "GGTCTC" # BsaI 76 | "N" 77 | "NNNN" # Device overhang (start) 78 | "(NNNN)" # Vector overhang (start) 79 | "(N" 80 | "GAGACG" # BsmBI 81 | "N*" # Placeholder sequence 82 | "CGTCTC" # BsmBI 83 | "N)" 84 | "(NNNN)" # Vector overhang (end) 85 | "NNNN" # Device overhang (end) 86 | "N" 87 | "GAGACC" # BsaI 88 | ) 89 | 90 | 91 | ### MODULES 92 | 93 | # TODO 94 | # class EcoFlexProduct(modules.Product): 95 | # """An EcoFlex MoClo product. 96 | # """ 97 | # 98 | # cutter = BsaI 99 | 100 | 101 | class EcoFlexEntry(modules.Entry): 102 | """An EcoFlex MoClo entry. 103 | 104 | EcoFlex entries are stored and shared as plasmids flanked by *BsaI* 105 | binding sites at both ends of the target sequence. 106 | """ 107 | 108 | cutter = BsaI 109 | 110 | 111 | class EcoFlexCassette(modules.Cassette): 112 | """An EcoFlex MoClo cassette. 113 | """ 114 | 115 | cutter = BsmBI 116 | 117 | 118 | class EcoFlexDevice(modules.Device): 119 | """An EcoFlex MoClo device. 120 | """ 121 | 122 | cutter = BsaI 123 | 124 | 125 | ### PARTS 126 | 127 | 128 | class EcoFlexPart(parts.AbstractPart): 129 | """An EcoFlex MoClo standard part. 130 | """ 131 | 132 | cutter = BsaI 133 | signature = NotImplemented 134 | 135 | 136 | class EcoFlexPromoter(EcoFlexPart, EcoFlexEntry): 137 | """An EcoFlex MoClo promoter. 138 | 139 | .. image: promoter.svg 140 | :align: center 141 | 142 | """ 143 | 144 | signature = ("CTAT", "GTAC") 145 | 146 | 147 | class EcoFlexRBS(EcoFlexPart, EcoFlexEntry): 148 | """An EcoFlex MoClo ribosome binding site. 149 | 150 | .. image:: rbs.svg 151 | :align: center 152 | 153 | Parts of this type contain a ribosome binding site (RBS). The last 154 | adenosine serves as the beginning of the start codon of the following CDS. 155 | """ 156 | 157 | signature = ("GTAC", "CATA") 158 | 159 | 160 | class EcoFlexTagLinker(EcoFlexPart, EcoFlexEntry): 161 | """An EcoFlex MoClo tag linker. 162 | 163 | .. image:: linker.svg 164 | :align: center 165 | 166 | Parts of this type also contain a RBS, but they allow adding a N-terminal 167 | tag sequence before the CDS. 168 | """ 169 | 170 | signature = ("GTAC", "TAAA") 171 | 172 | 173 | class EcoFlexTag(EcoFlexPart, EcoFlexEntry): 174 | """An EcoFlex MoClo N-terminal tag. 175 | 176 | .. image:: tag.svg 177 | :align: center 178 | 179 | Parts of this type typically contain tags that are added to the N-terminus 180 | of the translated protein, such as a *hexa histidine* or a *Strep(II)* tag. 181 | """ 182 | 183 | signature = ("TAAA", "CATA") 184 | 185 | 186 | class EcoFlexCodingSequence(EcoFlexPart, EcoFlexEntry): 187 | """An EcoFlex MoClo coding sequence. 188 | 189 | .. image:: cds.svg 190 | :align: center 191 | 192 | Parts of this type contain a coding sequence (CDS), with the start codon 193 | beginning on the upstream overhang. 194 | 195 | Caution: 196 | Although the start codon is located on the upstream overhang, a STOP 197 | codon is expected to be found within this part target sequence before 198 | the downstream overhang. 199 | """ 200 | 201 | signature = ("CATA", "TCGA") 202 | 203 | 204 | class EcoFlexTerminator(EcoFlexPart, EcoFlexEntry): 205 | """An EcoFlex MoClo terminator. 206 | 207 | .. image:: terminator.svg 208 | :align: center 209 | 210 | """ 211 | 212 | signature = ("TCGA", "TGTT") 213 | 214 | 215 | class EcoFlexPromoterRBS(EcoFlexPart, EcoFlexEntry): 216 | """An EcoFlex Moclo promoter followed by an RBS. 217 | 218 | .. image:: promoter-rbs.svg 219 | :align: center 220 | 221 | These parts contain a promoter (official parts use the T7 consensus) 222 | followed by a ribosome binding site, and possibly a proteic tag. 223 | """ 224 | 225 | signature = ("CTAT", "CATA") 226 | -------------------------------------------------------------------------------- /moclo/moclo/core/vectors.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """MoClo vector classes. 3 | 4 | A vector is a plasmidic DNA sequence that can hold a combination of modules of 5 | the same level to create a single module of the following level. Vectors 6 | contain a placeholder sequence that is replaced by the concatenation of the 7 | modules during the Golden Gate assembly. 8 | """ 9 | 10 | import typing 11 | 12 | from Bio.Seq import Seq 13 | from property_cached import cached_property 14 | 15 | from .. import errors 16 | from ._assembly import AssemblyManager 17 | from ._utils import cutter_check, add_as_source 18 | from ._structured import StructuredRecord 19 | 20 | if typing.TYPE_CHECKING: 21 | from typing import Any, MutableMapping, Union # noqa: F401 22 | from Bio.SeqRecord import SeqRecord # noqa: F401 23 | from Bio.Restriction.Restriction import RestrictionType # noqa: F401 24 | from .modules import AbstractModule # noqa: F401 25 | 26 | 27 | class AbstractVector(StructuredRecord): 28 | """An abstract modular cloning vector. 29 | """ 30 | 31 | _level = None # type: Union[None, int] 32 | cutter = NotImplemented # type: Union[NotImplemented, RestrictionType] 33 | 34 | def __new__(cls, *args, **kwargs): 35 | cutter_check(cls.cutter, name=cls.__name__) 36 | return super(AbstractVector, cls).__new__(cls) 37 | 38 | @classmethod 39 | def structure(cls): 40 | # type: () -> Text 41 | """Get the vector structure, as a DNA regex pattern. 42 | 43 | Warning: 44 | If overloading this method, the returned pattern must include 3 45 | capture groups to capture the following features: 46 | 47 | 1. The downstream (3') overhang sequence 48 | 2. The vector placeholder sequence 49 | 3. The upstream (5') overhang sequence 50 | 51 | """ 52 | downstream = cls.cutter.elucidate() 53 | upstream = str(Seq(downstream).reverse_complement()) 54 | return "".join( 55 | [ 56 | upstream.replace("^", ")(").replace("_", "("), 57 | "N*", 58 | downstream.replace("^", ")(").replace("_", ")"), 59 | ] 60 | ) 61 | 62 | def overhang_start(self): 63 | # type: () -> Seq 64 | """Get the upstream overhang of the vector sequence. 65 | """ 66 | return self._match.group(3).seq 67 | 68 | def overhang_end(self): 69 | # type: () -> Seq 70 | """Get the downstream overhang of the vector sequence. 71 | """ 72 | return self._match.group(1).seq 73 | 74 | def placeholder_sequence(self): 75 | # type: () -> SeqRecord 76 | """Get the placeholder sequence in the vector. 77 | 78 | The placeholder sequence is replaced by the concatenation of modules 79 | during the assembly. It often contains a dropout sequence, such as a 80 | GFP expression cassette that can be used to measure the progress of 81 | the assembly. 82 | """ 83 | if self.cutter.is_3overhang(): 84 | return self._match.group(2) + self.overhang_end() 85 | else: 86 | return self.overhang_start() + self._match.group(2) 87 | 88 | def target_sequence(self): 89 | # type: () -> SeqRecord 90 | """Get the target sequence in the vector. 91 | 92 | The target sequence if the part of the plasmid that is not discarded 93 | during the assembly (everything except the placeholder sequence). 94 | """ 95 | if self.cutter.is_3overhang(): 96 | start, end = self._match.span(2)[0], self._match.span(3)[1] 97 | else: 98 | start, end = self._match.span(1)[0], self._match.span(2)[1] 99 | return add_as_source(self.record, (self.record << start)[end - start :]) 100 | 101 | @cached_property 102 | def _match(self): 103 | _match = super(AbstractVector, self)._match 104 | if len(self.cutter.catalyse(_match.group(0).seq)) > 3: 105 | raise errors.IllegalSite(self.seq) 106 | return _match 107 | 108 | def assemble(self, module, *modules, **kwargs): 109 | # type: (AbstractModule, *AbstractModule, **Any) -> SeqRecord 110 | """Assemble the provided modules into the vector. 111 | 112 | Arguments: 113 | module (`~moclo.base.modules.AbstractModule`): a module to insert 114 | in the vector. 115 | modules (`~moclo.base.modules.AbstractModule`, optional): additional 116 | modules to insert in the vector. The order of the parameters 117 | is not important, since modules will be sorted by their start 118 | overhang in the function. 119 | 120 | Returns: 121 | `~Bio.SeqRecord.SeqRecord`: the assembled sequence with sequence 122 | annotations inherited from the vector and the modules. 123 | 124 | Raises: 125 | `~moclo.errors.DuplicateModules`: when two different modules share 126 | the same start overhang, leading in possibly non-deterministic 127 | constructs. 128 | `~moclo.errors.MissingModule`: when a module has an end overhang 129 | that is not shared by any other module, leading to a partial 130 | construct only 131 | `~moclo.errors.InvalidSequence`: when one of the modules does not 132 | match the required module structure (missing site, wrong 133 | overhang, etc.). 134 | `~moclo.errors.UnusedModules`: when some modules were not used 135 | during the assembly (mostly caused by duplicate parts). 136 | 137 | """ 138 | 139 | mgr = AssemblyManager( 140 | vector=self, 141 | modules=[module] + list(modules), 142 | name=kwargs.get("name", "assembly"), 143 | id_=kwargs.get("id", "assembly"), 144 | ) 145 | return mgr.assemble() 146 | 147 | 148 | class EntryVector(AbstractVector): 149 | """Level 0 vector. 150 | """ 151 | 152 | _level = 0 153 | 154 | 155 | class CassetteVector(AbstractVector): 156 | """Level 1 vector. 157 | """ 158 | 159 | _level = 1 160 | 161 | 162 | class DeviceVector(AbstractVector): 163 | """Level 2 vector. 164 | """ 165 | 166 | _level = 2 167 | --------------------------------------------------------------------------------