Skip to content

Commit

Permalink
using mypy for type checking
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffersonfparil committed Nov 8, 2024
1 parent 7084e14 commit e4e2543
Show file tree
Hide file tree
Showing 9 changed files with 429 additions and 568 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/test_pytest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 🧬
name: 🌱💚🧬

on:
push:
Expand All @@ -19,5 +19,9 @@ jobs:
python-version-file: "pyproject.toml"
- name: Install the project
run: uv sync --all-extras --dev
- name: Run tests
run: uv run pytest -s
- name: Run uv for formatting (via ruff), type checking (via mypy) and testing (via pytest)
run: |
uv run ruff format
uv run ruff check
uv run mypy */*.py
uv run pytest -s
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"ndarray"
]
}
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ cd gb/
pip install uv
pip install ruff
uv init .
uv add pytest numpy progressbar2 matplotlib polars scipy statsmodels click
ruff format
ruff check
pytest -s # or uv run pytest
uv add pytest mypy numpy progressbar2 matplotlib polars scipy statsmodels click
uv run ruff format
uv run ruff check
uv run mypy */*.py
uv run pytest -s
```
140 changes: 76 additions & 64 deletions data/error.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,82 @@
"""
Error classes
"""

from typing import Self


class Error:
code: list[int]
message: list[str]

def __init__(self, code: int, message: str) -> None:
if isinstance(code, list):
self.code = code
self.message = message
else:
self.code = [code]
self.message = [message]

def __getitem__(self, index) -> int:
return self.code[index]

def __repr__(self) -> str:
n = len(self.code)
out = []
for i in range(n):
out.append(str(self.code[i]) + ': ' + self.message[i])
return '< ' + '|'.join(out) + ' > @ ' + str(hex(id(self)))

def __str__(self) -> str:
n = len(self.code)
out = []
out.append('')
out.append('@@@@@@@@@@@@@@@')
out.append('@@@ Error/s @@@')
out.append('@@@@@@@@@@@@@@@')
for i in range(n):
out.append(f'- {self.code[i]}:\t {self.message[i]}')
return '\n'.join(out)

def __eq__(self, error) -> bool:
return (self.code == error.code) and (self.message == error.message)

def chain(self, errors: Self) -> None:
codes = errors.code.copy()
messages = errors.message.copy()
codes.extend(self.code)
messages.extend(self.message)
self.code = codes
self.message = messages
class Error(BaseException):
"""
Parent error class
"""

errors: list[Self]

def __init__(self: Self) -> None: ...
def __str__(self: Self) -> str:
if hasattr(self, 'errors'):
error_names = []
for e in self.errors:
error_names.append(str(type(e)).rsplit('.', maxsplit=-1)[-1].split("'")[0])
return ' | '.join(error_names)
else:
error_name = str(type(self)).rsplit('.', maxsplit=-1)[-1].split("'")[0]
return error_name

def chain(self: Self, other_error: Self) -> None:
"""
Chain errors by putting them on a list, i.e. errors: list[Error]
"""
if hasattr(self, 'errors') and hasattr(other_error, 'errors'):
self.errors.extend(other_error.errors)
elif hasattr(self, 'errors'):
self.errors.append(other_error)
elif hasattr(other_error, 'errors'):
self.errors = [self]
self.errors.append(other_error)
else:
self.errors = [self, other_error]


class LogicError(Error):
"""
Error in the logic of the code.
This means an unexpected error, i.e. an error we did not anticipate.
This is something we need to correctly classify or make a new error class for.
"""


class IncompatibleParameters(Error):
"""
Input parameters are incompatible, e.g.
- the number of entries and number of loci should be non-zero and positive,
- two vectors/matrices/arrays which should be the same size are not
"""


class RandomSamplingError(Error):
"""
Error in defining the parameters for the sampling distribution or
there are more items requested than there are items to choose from when sampling without replacement.
"""


class AlleleFreqOverUnderflow(Error):
"""
Allele frequencies per locus are less than zero or higher than one.
"""


def test_error():
error1 = Error(101, 'Error test')
error2 = Error(102, 'Another error')
error3 = Error(103, 'Yet another error')
print(error1)
print(error2)
print(error3)

error1.chain(error2)
print(error1)
assert len(error1.code) == 2
error1.chain(error3)
assert len(error1.code) == 3
error1
print(error1)
type(error1)

assert error1 == Error(
[103, 102, 101], ['Yet another error', 'Another error', 'Error test']
)
assert error2 == Error(102, 'Another error')
assert error3 == Error(103, 'Yet another error')
error1 = Error()
error2 = LogicError()
error3 = IncompatibleParameters()
assert str(error1) == 'Error'
assert str(error2) == 'LogicError'
assert str(error3) == 'IncompatibleParameters'
error1.chain(error3)
assert str(error1) == 'Error | IncompatibleParameters'
error2.chain(error3)
assert str(error2) == 'LogicError | IncompatibleParameters'
error1.chain(error2)
assert str(error1) == 'Error | IncompatibleParameters | LogicError | IncompatibleParameters'
62 changes: 31 additions & 31 deletions data/genotype.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
from data.type_aliases import VectorU64, VectorF64, VectorSTR, MatrixF64, MatrixB
import numpy as np
# from data.error import Error


class Genomes:
n: int
p: int
m: int
a: int
b: VectorF64
entries: VectorSTR
chromosomes: VectorSTR
positions: VectorU64
alleles: VectorSTR
X: MatrixF64
mask: MatrixB
n: int
p: int
m: int
a: int
b: np.ndarray
entries: np.ndarray
chromosomes: np.ndarray
positions: np.ndarray
alleles: np.ndarray
X: np.ndarray
mask: np.ndarray

def __init__(self) -> None:
self.n = 1
self.p = 1
self.m = 1
self.a = 1
self.b = [1.00]
self.entries = ['dummy_entry_1']
self.chromosomes = ['chromosome_1']
self.positions = [123_456_789]
self.alleles = ['allele 1|allele_2|allele_3\tallele_2']
self.X = [[1.00]]
self.mask = [[False]]
def __init__(self) -> None:
self.n = 1
self.p = 1
self.m = 1
self.a = 1
self.b = np.array([1.00])
self.entries = np.array(['dummy_entry_1'])
self.chromosomes = np.array(['chromosome_1'])
self.positions = np.array([123_456_789])
self.alleles = np.array(['allele 1|allele_2|allele_3\tallele_2'])
self.X = np.array([[1.00]])
self.mask = np.array([[False]])


def test_genotype():
genotypes = Genomes()
print(genotypes.X)
assert genotypes.n == 1
assert genotypes.n == genotypes.p
assert genotypes.p == genotypes.m
assert genotypes.m == genotypes.a
assert len(genotypes.b) == 1
genotypes = Genomes()
print(genotypes.X)
assert genotypes.n == 1
assert genotypes.n == genotypes.p
assert genotypes.p == genotypes.m
assert genotypes.m == genotypes.a
assert len(genotypes.b) == 1
34 changes: 17 additions & 17 deletions data/phenotype.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
from data.type_aliases import VectorSTR, MatrixF64, MatrixB
import numpy as np
# from data.error import Error


class Phenomes:
n: int
q: int
entries: VectorSTR
traits: VectorSTR
Y: MatrixF64
mask: MatrixB
n: int
q: int
entries: np.ndarray
traits: np.ndarray
Y: np.ndarray
mask: np.ndarray

def __init__(self) -> None:
self.n = 1
self.q = 1
self.entries = ['dummy_entry_1']
self.traits = ['dummy_trait_1']
self.Y = [[0.00]]
self.mask = [[False]]
def __init__(self) -> None:
self.n = 1
self.q = 1
self.entries = np.array(['dummy_entry_1'])
self.traits = np.array(['dummy_trait_1'])
self.Y = np.array([[0.00]])
self.mask = np.array([[False]])


def test_phenomes():
Y = Phenomes()
assert Y.n == 1
assert Y.n == Y.q
Y = Phenomes()
assert Y.n == 1
assert Y.n == Y.q
Loading

0 comments on commit e4e2543

Please sign in to comment.