From 59392f08acfb138d0f304cfc416486b606cdd9df Mon Sep 17 00:00:00 2001 From: veghp Date: Fri, 12 Jul 2024 16:52:12 +0100 Subject: [PATCH] Commit first working prototype --- .bumpversion.cfg | 8 ++ .github/workflows/build.yml | 25 ++++++ .gitignore | 39 +--------- LICENSE | 21 +++++ README.md | 42 +++++++++- dna_mutator/Mutator.py | 148 ++++++++++++++++++++++++++++++++++++ dna_mutator/__init__.py | 1 + dna_mutator/version.py | 1 + images/egf.png | Bin 0 -> 26795 bytes pypi-readme.rst | 33 ++++++++ setup.py | 20 +++++ tests/test_Mutator.py | 7 ++ 12 files changed, 308 insertions(+), 37 deletions(-) create mode 100644 .bumpversion.cfg create mode 100644 .github/workflows/build.yml create mode 100644 LICENSE create mode 100644 dna_mutator/Mutator.py create mode 100644 dna_mutator/__init__.py create mode 100644 dna_mutator/version.py create mode 100644 images/egf.png create mode 100644 pypi-readme.rst create mode 100644 setup.py create mode 100644 tests/test_Mutator.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..86430d7 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,8 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:README.md] + +[bumpversion:file:dna_mutator/version.py] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7a21eb1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +name: build + +on: [push, workflow_dispatch] + +jobs: + build: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov + - name: Test pip installation + run: | + pip install -e . + - name: Test with pytest + run: | + python -m pytest --cov dna_mutator diff --git a/.gitignore b/.gitignore index 82f9275..b6e4761 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ parts/ sdist/ var/ wheels/ +pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -49,7 +50,6 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ -cover/ # Translations *.mo @@ -72,7 +72,6 @@ instance/ docs/_build/ # PyBuilder -.pybuilder/ target/ # Jupyter Notebook @@ -83,9 +82,7 @@ profile_default/ ipython_config.py # pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -94,24 +91,7 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +# PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff @@ -147,16 +127,3 @@ dmypy.json # Pyre type checker .pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..beb22c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Edinburgh Genome Foundry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 058d576..9b561c2 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# Mutator \ No newline at end of file +

+EGF logo +

+ +# DNA Mutator + +![version](https://img.shields.io/badge/current_version-0.1.0-blue) +[![build](https://github.com/Edinburgh-Genome-Foundry/dna_mutator/actions/workflows/build.yml/badge.svg)](https://github.com/Edinburgh-Genome-Foundry/dna_mutator/actions/workflows/build.yml) + +Create variants of DNA sequences. + +This repository is based on the software code of a dissertation project (B237870) for the MSc Bioinformatics program at the University of Edinburgh. + +## Install + +```bash +pip install git+https://github.com/Edinburgh-Genome-Foundry/mutator.git +``` + +## Usage + +```python +import dna_mutator as mutator +record = mutator.Mutator.read_genbank("EGF.gb") +mut = mutator.Mutator(record) +mut.DelN() +mut.write_all_records("variants") +``` + +## Versioning + +DNA Mutator uses the [semantic versioning](https://semver.org) scheme. + +## License = MIT + +DNA Mutator is free/libre and open-source software, which means the users have the freedom to run, study, change and distribute the software. + +DNA Mutator was written at the [Edinburgh Genome Foundry](https://edinburgh-genome-foundry.github.io/) +by [B237870](https://github.com/B237870-2024) and [Peter Vegh](https://github.com/veghp). + +Copyright 2024 Edinburgh Genome Foundry, University of Edinburgh diff --git a/dna_mutator/Mutator.py b/dna_mutator/Mutator.py new file mode 100644 index 0000000..479656f --- /dev/null +++ b/dna_mutator/Mutator.py @@ -0,0 +1,148 @@ +import os +import random + +import pandas + +from Bio import SeqIO +from Bio.Seq import Seq, MutableSeq +from Bio.SeqRecord import SeqRecord +from Bio.SeqFeature import SeqFeature, FeatureLocation + + +class Mutator: + """Class to generate simulations of structural and single nucleotide variants. + + + **Parameters** + + **reference** + > A `SeqRecord` instance. + + **library_size** + > Library size (`int`). + """ + + def __init__(self, reference, library_size=10): + self.reference = reference + self.library_size = library_size + self.variant_records = [] + + def write_sample_sheet(self, csv_file): + """Create a sample sheet (for use with Sequeduct)""" + barcode_dir = [ + "barcode" + ("{0:02d}".format(i + 1)) + for i in range(len(self.variant_records)) + ] + + variants = [variant_record.id for variant_record in self.variant_records] + df_variants = pandas.DataFrame({"Sample": variants, "Barcode_dir": barcode_dir}) + df_variants.to_csv(csv_file, index=False) + + @staticmethod + def subtract_bases(seq, pos, n): + """Substract N bases from a sequence + + + **Parameters** + + **seq** + > `Seq` instance. + + **pos** + > Location of change (`int`). + + **n** + > Number of bases to subtract (`int`). + """ + modified_sequence = MutableSeq(seq) + deleted_sequence = modified_sequence[pos : pos + n] + del modified_sequence[pos : pos + n] + + return ( + modified_sequence, + pos, + deleted_sequence, + ) + + @staticmethod + def get_random_pos(record, n=1): + """Get n different random positions in a record""" + positions = random.sample(range(0, len(record)), n) + + return positions + + @staticmethod + def read_genbank(genbank, use_file_name_as_id=True): + """Get the reference sequence and features from input file + + **Parameters** + + **genbank** + > Path to Genbank file (`str`). + + **use_file_name_as_id** + > Replace record id and name with the filename (`bool`). + """ + record = SeqIO.read(genbank, "genbank") + if use_file_name_as_id: + record.name = os.path.splitext(os.path.basename(genbank))[0] + record.id = record.name + + return record + + @staticmethod + def write_genbank(record, file_name): + """Write SeqRecord to a Genbank file""" + SeqIO.write(record, file_name, "gb") + + def write_all_records(self, dir_name): + """Write original record and all variants into a directory""" + extension = ".gb" # standard file ext for GenBank files + os.mkdir(dir_name) + # ORIGINAL REFERENCE RECORD + ref_path = os.path.join(dir_name, self.reference.id + extension) + self.write_genbank(self.reference, ref_path) + # VARIANTS + for variant in self.variant_records: + variant_path = os.path.join(dir_name, variant.id) + self.write_genbank(variant, variant_path + extension) + + def DelN(self, bases=1): + """Simulate N base deletion""" + positions = self.get_random_pos(self.reference, n=self.library_size) + for i in range(self.library_size): + position = positions[i] + modified_sequence, position, deleted_sequence = self.subtract_bases( + self.reference.seq, position, bases + ) + if len(deleted_sequence) == 1: # show the letter if there's only one + suffix = str(deleted_sequence) + else: + suffix = str(len(deleted_sequence)) + # We append the original name according to nomenclature: + variant_name = self.reference.id + "_" + str(position) + "D" + suffix + variant_record = SeqRecord( + Seq(modified_sequence), + id=variant_name, + name=variant_name, + annotations={"molecule_type": "DNA", "topology": "circular"}, + ) + label = "@mutator(del)" + description = ( + "Deletion in position " + + str(position) + + " of " + + str(bases) + + " bases (" + + str(deleted_sequence) + + ")" + ) + feature = SeqFeature( + FeatureLocation(position, position), + type="misc_feature", + id="@mutator", + qualifiers={"label": label, "note": description}, + ) + variant_record.features.append(feature) + + self.variant_records.append(variant_record) diff --git a/dna_mutator/__init__.py b/dna_mutator/__init__.py new file mode 100644 index 0000000..e2aa00b --- /dev/null +++ b/dna_mutator/__init__.py @@ -0,0 +1 @@ +from .Mutator import Mutator diff --git a/dna_mutator/version.py b/dna_mutator/version.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/dna_mutator/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/images/egf.png b/images/egf.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0f67e82d39b83ea0752ad706d0e19d9d06936e GIT binary patch literal 26795 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 z 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 + + +**Github page:** + +``_ + + +**License:** MIT, Copyright 2024 Edinburgh Genome Foundry, University of Edinburgh + + +More biology software +--------------------- + +.. image:: https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/Edinburgh-Genome-Foundry.github.io/master/static/imgs/logos/egf-codon-horizontal.png + :target: https://edinburgh-genome-foundry.github.io/ + +DNA Mutator is part of the `EGF Codons `_ synthetic biology software suite for DNA design, manufacturing and validation. diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..425474a --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup, find_packages + +version = {} +with open("dna_mutator/version.py") as fp: + exec(fp.read(), version) + +setup( + name="dna_mutator", + version=version["__version__"], + author="B237870", + author_email="egf-software@ed.ac.uk", + description="Create variants of DNA sequences", + long_description=open("pypi-readme.rst").read(), + long_description_content_type="text/x-rst", + license="MIT", + keywords="biology dna", + packages=find_packages(exclude="docs"), + include_package_data=True, + install_requires=["biopython"], +) diff --git a/tests/test_Mutator.py b/tests/test_Mutator.py new file mode 100644 index 0000000..91579e4 --- /dev/null +++ b/tests/test_Mutator.py @@ -0,0 +1,7 @@ +import pytest + +import dna_mutator + + +def test_Mutator(): + pass