Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/base arch #8

Merged
merged 17 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ updates:
target-branch: dev
assignees:
- "sadrasabouri"
- "AHReccese"
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-latest, macOS-latest]
os: [ubuntu-20.04, windows-2022, macOS-13]
python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0, 3.11.0, 3.12.0]
steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 3 additions & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Authors #

----------
- Sadra Sabouri - Open Science Laboratory ([Github](https://github.com/sadrasabouri)) **
- Sepand Haghighi - Open Science Laboratory ([Github](https://github.com/sepandhaghighi))
- Sadra Sabouri - Open Science Laboratory ([Github](https://github.com/sadrasabouri))
- AmirHosein Rostami - Open Science Laboratory ([Github](https://github.com/AHReccese))

** **Maintainer**

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Added
### Changed
## [0.1] - 2024-06-05
### Added
- Molecular Weight Calculation
- Sequence Validation
- OPR class

[Unreleased]: https://github.com/openscilab/opr/compare/v0.1...dev
[0.1]: https://github.com/openscilab/opr/compare/3598e8b...v0.1
6 changes: 6 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
setuptools>=40.8.0
vulture>=1.0
bandit>=1.5.1
pydocstyle>=3.0.0
pytest>=4.3.1
pytest-cov>=2.6.1
1 change: 1 addition & 0 deletions opr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""OPR modules."""
from .opr_param import OPR_VERSION
from .opr_obj import OPR

__version__ = OPR_VERSION
8 changes: 8 additions & 0 deletions opr/opr_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""OPR errors."""


class OPRBaseError(Exception):
"""OPR base error class."""

pass
95 changes: 95 additions & 0 deletions opr/opr_obj.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""OPR modules."""
from .opr_error import OPRBaseError
from .opr_param import VALID_BASES
from .opr_param import PRIMER_SEQUENCE_TYPE_ERROR, PRIMER_SEQUENCE_LENGTH_ERROR, PRIMER_SEQUENCE_VALID_BASES_ERROR, PRIMER_SEQUENCE_VALID_GC_CONTENT_RANGE_ERROR
from .opr_param import PRIMER_LOWER_LENGTH, PRIMER_HIGHEST_LENGTH, PRIMER_LOWEST_GC_RANGE, PRIMER_HIGHEST_GC_RANGE
from .opr_param import PRIMER_READ_ONLY_ATTRIBUTE_ERROR, PRIMER_NOT_REMOVABLE_ATTRIBUTE_ERROR
from .opr_param import A_WEIGHT, T_WEIGHT, C_WEIGHT, G_WEIGHT, ANHYDROUS_MOLECULAR_WEIGHT_CONSTANT

class OPR:
"""
The OPR class facilitates working with the primer sequence.

>>> oprimer = OPR("ATCGATCGATCGATCGAT")
>>> oprimer.molecular_weight
"""

def __init__(self, primer_sequence):
"""
Initialize the Optimized Primer(OPR) instance.

:param primer_sequence: primer nucleotides sequence
:type primer_sequence: str
:return: an instance of the OPR class
"""
self._sequence = OPR.validate_primer(primer_sequence)
self._molecular_weight = None

@staticmethod
def validate_primer(primer_sequence):
"""
Validate the given primer sequence.

:param primer_sequence: primer nucleotides sequence
:type primer_sequence: any
:return: an uppercased primer sequence
"""
if not isinstance(primer_sequence, str):
raise OPRBaseError(PRIMER_SEQUENCE_TYPE_ERROR)
primer_sequence = primer_sequence.upper()

if len(primer_sequence) < PRIMER_LOWER_LENGTH or len(primer_sequence) > PRIMER_HIGHEST_LENGTH:
raise OPRBaseError(PRIMER_SEQUENCE_LENGTH_ERROR)

if not all(base in VALID_BASES for base in primer_sequence):
raise OPRBaseError(PRIMER_SEQUENCE_VALID_BASES_ERROR)

gc_count = primer_sequence.count('G') + primer_sequence.count('C')
gc_content = gc_count / len(primer_sequence)

if gc_content < PRIMER_LOWEST_GC_RANGE or gc_content > PRIMER_HIGHEST_GC_RANGE:
raise OPRBaseError(PRIMER_SEQUENCE_VALID_GC_CONTENT_RANGE_ERROR)
return primer_sequence

@property
def sequence(self):
"""
Return the primer sequence.

:return: primer sequence
"""
return self._sequence

@sequence.setter
def sequence(self, _):
raise OPRBaseError(PRIMER_READ_ONLY_ATTRIBUTE_ERROR)

@sequence.deleter
def sequence(self, _):
raise OPRBaseError(PRIMER_NOT_REMOVABLE_ATTRIBUTE_ERROR)

@property
def molecular_weight(self):
"""
Calculate(if needed) the molecular weight.

:return: molecular weight
"""
if self._molecular_weight:
return self._molecular_weight
a_count = self._sequence.count('A')
t_count = self._sequence.count('T')
c_count = self._sequence.count('C')
g_count = self._sequence.count('G')
# Anhydrous Molecular Weight = (An x 313.21) + (Tn x 304.2) + (Cn x 289.18) + (Gn x 329.21) - 61.96
self._molecular_weight = (a_count * A_WEIGHT) + (t_count * T_WEIGHT) + (c_count * C_WEIGHT) + (g_count * G_WEIGHT) - ANHYDROUS_MOLECULAR_WEIGHT_CONSTANT
return self._molecular_weight

@molecular_weight.setter
def molecular_weight(self, _):
raise OPRBaseError(PRIMER_READ_ONLY_ATTRIBUTE_ERROR)

@molecular_weight.deleter
def molecular_weight(self, _):
self._molecular_weight = None
20 changes: 20 additions & 0 deletions opr/opr_param.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# -*- coding: utf-8 -*-
"""Parameters and constants."""
OPR_VERSION = "0.1"
VALID_BASES = set('ATCG')

PRIMER_LOWER_LENGTH = 18
PRIMER_HIGHEST_LENGTH = 30
PRIMER_LOWEST_GC_RANGE = 0.3
PRIMER_HIGHEST_GC_RANGE = 0.8

A_WEIGHT = 313.21
T_WEIGHT = 304.2
C_WEIGHT = 289.18
G_WEIGHT = 329.21
ANHYDROUS_MOLECULAR_WEIGHT_CONSTANT = 61.96

PRIMER_SEQUENCE_TYPE_ERROR = "Primer sequence should be a string variable."
PRIMER_SEQUENCE_LENGTH_ERROR = "Primer length should be between 18 and 30 nucleotides."
PRIMER_SEQUENCE_VALID_BASES_ERROR = "Primer sequence should only contain the nucleotide bases A, T, C, and G."
PRIMER_SEQUENCE_VALID_GC_CONTENT_RANGE_ERROR = "Primer GC content should be between 30% and 80%."
PRIMER_READ_ONLY_ATTRIBUTE_ERROR = "This attribute is read-only."
PRIMER_NOT_REMOVABLE_ATTRIBUTE_ERROR = "This attribute is not removable."

2 changes: 2 additions & 0 deletions otherfiles/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ about:
Repo: https://github.com/openscilab/opr
extra:
recipe-maintainers:
- sepandhaghighi
- AHReccese
- sadrasabouri
7 changes: 7 additions & 0 deletions tests/test_molecular_weight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from opr.opr_obj import OPR

TEST_CASE_NAME = "molecular weight calculation testcase"

def test_mwc():
oprimer = OPR("ATCGATCGATCGATCGAT")
assert round(oprimer.molecular_weight, 1) == 5498.7
Loading