Skip to content

Commit

Permalink
Format code with Black
Browse files Browse the repository at this point in the history
  • Loading branch information
gerlero committed Feb 18, 2024
1 parent 85e7a0d commit 8e1eefc
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 104 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ on:
workflow_dispatch:

jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Format code with Black
uses: psf/black@stable

typing:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Codecov](https://codecov.io/gh/microfluidica/electrolytes/branch/main/graph/badge.svg)](https://codecov.io/gh/microfluidica/electrolytes)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/5697b1e4c4a9790ece607654e6c02a160620c7e1/docs/badge/v2.json)](https://pydantic.dev)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![PyPI](https://img.shields.io/pypi/v/electrolytes)](https://pypi.org/project/electrolytes/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/electrolytes)](https://pypi.org/project/electrolytes/)
[![Docker image](https://img.shields.io/badge/docker%20image-microfluidica%2Felectrolytes-0085a0)](https://hub.docker.com/r/microfluidica/electrolytes/)
Expand Down
104 changes: 70 additions & 34 deletions electrolytes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pkgutil
from pathlib import Path
from typing import Collection, Iterator, List, Sequence, Dict, Optional, Any

if sys.version_info >= (3, 9):
from typing import Annotated
else:
Expand All @@ -13,7 +14,14 @@
from contextlib import ContextDecorator
from warnings import warn

from pydantic import BaseModel, Field, field_validator, ValidationInfo, model_validator, TypeAdapter
from pydantic import (
BaseModel,
Field,
field_validator,
ValidationInfo,
model_validator,
TypeAdapter,
)
from filelock import FileLock
from typer import get_app_dir

Expand All @@ -23,43 +31,55 @@
class Constituent(BaseModel, populate_by_name=True, frozen=True):
id: Optional[int] = None
name: str
u_neg: Annotated[List[float], Field(alias="uNeg")] = [] # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
u_pos: Annotated[List[float], Field(alias="uPos")] = [] # [+1, +2, +3, ..., +pos_count]
pkas_neg: Annotated[List[float], Field(alias="pKaNeg")] = [] # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
pkas_pos: Annotated[List[float], Field(alias="pKaPos")] = [] # [+1, +2, +3, ..., +pos_count]
neg_count: Annotated[int, Field(alias="negCount", validate_default=True)] = None # type: ignore
pos_count: Annotated[int, Field(alias="posCount", validate_default=True)] = None # type: ignore

u_neg: Annotated[List[float], Field(alias="uNeg")] = (
[]
) # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
u_pos: Annotated[List[float], Field(alias="uPos")] = (
[]
) # [+1, +2, +3, ..., +pos_count]
pkas_neg: Annotated[List[float], Field(alias="pKaNeg")] = (
[]
) # [-neg_count, -neg_count+1, -neg_count+2, ..., -1]
pkas_pos: Annotated[List[float], Field(alias="pKaPos")] = (
[]
) # [+1, +2, +3, ..., +pos_count]
neg_count: Annotated[int, Field(alias="negCount", validate_default=True)] = None # type: ignore
pos_count: Annotated[int, Field(alias="posCount", validate_default=True)] = None # type: ignore

def mobilities(self) -> Sequence[float]:
n = max(self.neg_count, self.pos_count, 3)
ret = [0.0]*(n - self.pos_count) \
+ [u*1e-9 for u in self.u_pos[::-1]] \
+ [u*1e-9 for u in self.u_neg[::-1]] \
+ [0.0]*(n - self.neg_count)
assert len(ret) == 2*n
ret = (
[0.0] * (n - self.pos_count)
+ [u * 1e-9 for u in self.u_pos[::-1]]
+ [u * 1e-9 for u in self.u_neg[::-1]]
+ [0.0] * (n - self.neg_count)
)
assert len(ret) == 2 * n
return ret

def pkas(self) -> Sequence[float]:
n = max(self.neg_count, self.pos_count, 3)
ret = [self._default_pka(c) for c in range(n, self.pos_count, -1)] \
+ self.pkas_pos[::-1] \
+ self.pkas_neg[::-1] \
+ [self._default_pka(-c) for c in range(self.neg_count+1, n+1)]
assert len(ret) == 2*n
ret = (
[self._default_pka(c) for c in range(n, self.pos_count, -1)]
+ self.pkas_pos[::-1]
+ self.pkas_neg[::-1]
+ [self._default_pka(-c) for c in range(self.neg_count + 1, n + 1)]
)
assert len(ret) == 2 * n
return ret

def diffusivity(self) -> float:
mobs = []
try:
mobs.append(self.u_neg[-1]*1e-9)
mobs.append(self.u_neg[-1] * 1e-9)
except IndexError:
pass
try:
mobs.append(self.u_pos[0]*1e-9)
mobs.append(self.u_pos[0] * 1e-9)
except IndexError:
pass

return max(mobs, default=0)*8.314*300/96485
return max(mobs, default=0) * 8.314 * 300 / 96485

@staticmethod
def _default_pka(charge: int) -> float:
Expand All @@ -69,13 +89,12 @@ def _default_pka(charge: int) -> float:
else:
return -charge


@field_validator("name")
def _normalize_db1_names(cls, v: str, info: ValidationInfo) -> str:
if info.context and info.context.get("fix", None) == "db1":
v = v.replace(" ", "_").replace("Cl-", "CHLORO")
return v

@field_validator("name")
def _no_whitespace(cls, v: str, info: ValidationInfo) -> str:
parts = v.split()
Expand All @@ -95,7 +114,7 @@ def _pka_lengths(cls, v: List[float], info: ValidationInfo) -> List[float]:
if len(v) != len(info.data[f"u_{info.field_name[5:]}"]):
raise ValueError(f"len({info.field_name}) != len(u_{info.field_name[5:]})")
return v

@field_validator("neg_count", "pos_count", mode="before")
def _counts(cls, v: Optional[int], info: ValidationInfo) -> int:
assert isinstance(info.field_name, str)
Expand All @@ -109,25 +128,33 @@ def _counts(cls, v: Optional[int], info: ValidationInfo) -> int:
def _pkas_not_increasing(self) -> "Constituent":
pkas = [*self.pkas_neg, *self.pkas_pos]

if not all(x>=y for x, y in zip(pkas, pkas[1:])):
if not all(x >= y for x, y in zip(pkas, pkas[1:])):
raise ValueError("pKa values must not increase with charge")

return self


_StoredConstituents = TypeAdapter(Dict[str, List[Constituent]])

def _load_constituents(data: bytes, context: Optional[Dict[str,str]]=None) -> List[Constituent]:

def _load_constituents(
data: bytes, context: Optional[Dict[str, str]] = None
) -> List[Constituent]:
return _StoredConstituents.validate_json(data, context=context)["constituents"]


def _dump_constituents(constituents: List[Constituent]) -> bytes:
return _StoredConstituents.dump_json({"constituents": constituents}, by_alias=True, indent=4)
return _StoredConstituents.dump_json(
{"constituents": constituents}, by_alias=True, indent=4
)


class _Database(ContextDecorator):
def __init__(self, user_constituents_file: Path) -> None:
self._user_constituents_file = user_constituents_file
self._user_constituents_lock = FileLock(self._user_constituents_file.with_suffix(".lock"))
self._user_constituents_lock = FileLock(
self._user_constituents_file.with_suffix(".lock")
)
self._user_constituents_dirty = False

@cached_property
Expand All @@ -146,9 +173,12 @@ def _user_constituents(self) -> Dict[str, Constituent]:
except OSError:
return {}
try:
user_constituents = _load_constituents(user_data)
user_constituents = _load_constituents(user_data)
except Exception as e:
warn(f"failed to load user constituents from {self._user_constituents_file}: {type(e).__name__}", RuntimeWarning)
warn(
f"failed to load user constituents from {self._user_constituents_file}: {type(e).__name__}",
RuntimeWarning,
)
return {}
return {c.name: c for c in user_constituents}

Expand All @@ -168,18 +198,22 @@ def _save_user_constituents(self) -> None:

def __enter__(self) -> None:
if not self._user_constituents_lock.is_locked:
Path(self._user_constituents_lock.lock_file).parent.mkdir(parents=True, exist_ok=True) # https://github.com/tox-dev/py-filelock/issues/176
Path(self._user_constituents_lock.lock_file).parent.mkdir(
parents=True, exist_ok=True
) # https://github.com/tox-dev/py-filelock/issues/176
self._invalidate_user_constituents()
self._user_constituents_lock.acquire()

def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
try:
if self._user_constituents_lock.lock_counter == 1 and self._user_constituents_dirty:
if (
self._user_constituents_lock.lock_counter == 1
and self._user_constituents_dirty
):
self._save_user_constituents()
finally:
self._user_constituents_lock.release()


def __iter__(self) -> Iterator[str]:
yield from sorted([*self._default_constituents, *self._user_constituents])

Expand All @@ -196,7 +230,9 @@ def add(self, constituent: Constituent) -> None:
self._user_constituents[constituent.name] = constituent
self._user_constituents_dirty = True
else:
warn(f"{constituent.name}: component was not added (name already exists in database)")
warn(
f"{constituent.name}: component was not added (name already exists in database)"
)

def __delitem__(self, name: str) -> None:
name = name.upper()
Expand Down
Loading

0 comments on commit 8e1eefc

Please sign in to comment.