diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23ff6b2..36e6eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/README.md b/README.md index f6a7375..199f263 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/electrolytes/__init__.py b/electrolytes/__init__.py index ba371b4..461e97f 100644 --- a/electrolytes/__init__.py +++ b/electrolytes/__init__.py @@ -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: @@ -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 @@ -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: @@ -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() @@ -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) @@ -109,7 +128,7 @@ 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 @@ -117,17 +136,25 @@ def _pkas_not_increasing(self) -> "Constituent": _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 @@ -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} @@ -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]) @@ -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() diff --git a/electrolytes/__main__.py b/electrolytes/__main__.py index f3b3f1a..2caba99 100644 --- a/electrolytes/__main__.py +++ b/electrolytes/__main__.py @@ -1,5 +1,6 @@ import sys from typing import Tuple, List, Optional + if sys.version_info >= (3, 9): from typing import Annotated else: @@ -16,25 +17,36 @@ def complete_name(incomplete: str) -> List[str]: return [name for name in database if name.startswith(incomplete.upper())] + def complete_name_user_defined(incomplete: str) -> List[str]: - return [name for name in database.user_defined() if name.startswith(incomplete.upper())] + return [ + name for name in database.user_defined() if name.startswith(incomplete.upper()) + ] @app.command() -def add(name: Annotated[str, typer.Argument(autocompletion=complete_name_user_defined)], - p1: Annotated[Tuple[float, float], typer.Option("+1", help="Mobility (*1e-9) and pKa for +1", show_default=False)] = (None, None), # type: ignore - p2: Annotated[Tuple[float, float], typer.Option("+2", help="Mobility (*1e-9) and pKa for +2", show_default=False)] = (None, None), # type: ignore - p3: Annotated[Tuple[float, float], typer.Option("+3", help="Mobility (*1e-9) and pKa for +3", show_default=False)] = (None, None), # type: ignore - p4: Annotated[Tuple[float, float], typer.Option("+4", help="Mobility (*1e-9) and pKa for +4", show_default=False)] = (None, None), # type: ignore - p5: Annotated[Tuple[float, float], typer.Option("+5", help="Mobility (*1e-9) and pKa for +5", show_default=False)] = (None, None), # type: ignore - p6: Annotated[Tuple[float, float], typer.Option("+6", help="Mobility (*1e-9) and pKa for +6", show_default=False)] = (None, None), # type: ignore - m1: Annotated[Tuple[float, float], typer.Option("-1", help="Mobility (*1e-9) and pKa for -1", show_default=False)] = (None, None), # type: ignore - m2: Annotated[Tuple[float, float], typer.Option("-2", help="Mobility (*1e-9) and pKa for -2", show_default=False)] = (None, None), # type: ignore - m3: Annotated[Tuple[float, float], typer.Option("-3", help="Mobility (*1e-9) and pKa for -3", show_default=False)] = (None, None), # type: ignore - m4: Annotated[Tuple[float, float], typer.Option("-4", help="Mobility (*1e-9) and pKa for -4", show_default=False)] = (None, None), # type: ignore - m5: Annotated[Tuple[float, float], typer.Option("-5", help="Mobility (*1e-9) and pKa for -5", show_default=False)] = (None, None), # type: ignore - m6: Annotated[Tuple[float, float], typer.Option("-6", help="Mobility (*1e-9) and pKa for -6", show_default=False)] = (None, None), # type: ignore - force: Annotated[bool, typer.Option("-f", help="Do not prompt before replacing a user-defined component with the same name")] = False) -> None: +def add( + name: Annotated[str, typer.Argument(autocompletion=complete_name_user_defined)], + p1: Annotated[Tuple[float, float], typer.Option("+1", help="Mobility (*1e-9) and pKa for +1", show_default=False)] = (None, None), # type: ignore + p2: Annotated[Tuple[float, float], typer.Option("+2", help="Mobility (*1e-9) and pKa for +2", show_default=False)] = (None, None), # type: ignore + p3: Annotated[Tuple[float, float], typer.Option("+3", help="Mobility (*1e-9) and pKa for +3", show_default=False)] = (None, None), # type: ignore + p4: Annotated[Tuple[float, float], typer.Option("+4", help="Mobility (*1e-9) and pKa for +4", show_default=False)] = (None, None), # type: ignore + p5: Annotated[Tuple[float, float], typer.Option("+5", help="Mobility (*1e-9) and pKa for +5", show_default=False)] = (None, None), # type: ignore + p6: Annotated[Tuple[float, float], typer.Option("+6", help="Mobility (*1e-9) and pKa for +6", show_default=False)] = (None, None), # type: ignore + m1: Annotated[Tuple[float, float], typer.Option("-1", help="Mobility (*1e-9) and pKa for -1", show_default=False)] = (None, None), # type: ignore + m2: Annotated[Tuple[float, float], typer.Option("-2", help="Mobility (*1e-9) and pKa for -2", show_default=False)] = (None, None), # type: ignore + m3: Annotated[Tuple[float, float], typer.Option("-3", help="Mobility (*1e-9) and pKa for -3", show_default=False)] = (None, None), # type: ignore + m4: Annotated[Tuple[float, float], typer.Option("-4", help="Mobility (*1e-9) and pKa for -4", show_default=False)] = (None, None), # type: ignore + m5: Annotated[Tuple[float, float], typer.Option("-5", help="Mobility (*1e-9) and pKa for -5", show_default=False)] = (None, None), # type: ignore + m6: Annotated[Tuple[float, float], typer.Option("-6", help="Mobility (*1e-9) and pKa for -6", show_default=False)] = (None, None), # type: ignore + force: Annotated[ + bool, + typer.Option( + "-f", + help="Do not prompt before replacing a user-defined component with the same name", + ), + ] = False, +) -> None: """Store a user-defined component in the database.""" name = name.upper() @@ -45,7 +57,7 @@ def add(name: Annotated[str, typer.Argument(autocompletion=complete_name_user_de neg: List[Tuple[float, float]] = [] any_omitted = False - for i,m in enumerate([m1, m2, m3, m4, m5, m6]): + for i, m in enumerate([m1, m2, m3, m4, m5, m6]): if m[0] is None: assert m[1] is None any_omitted = True @@ -57,7 +69,7 @@ def add(name: Annotated[str, typer.Argument(autocompletion=complete_name_user_de pos: List[Tuple[float, float]] = [] any_omitted = False - for i,p in enumerate([p1, p2, p3, p4, p5, p6]): + for i, p in enumerate([p1, p2, p3, p4, p5, p6]): if p[0] is None: assert p[1] is None any_omitted = True @@ -67,11 +79,13 @@ def add(name: Annotated[str, typer.Argument(autocompletion=complete_name_user_de else: pos.append(p) - constituent = Constituent(name=name, - u_neg=[x[0] for x in neg], - u_pos=[x[0] for x in pos], - pkas_neg=[x[1] for x in neg], - pkas_pos=[x[1] for x in pos]) + constituent = Constituent( + name=name, + u_neg=[x[0] for x in neg], + u_pos=[x[0] for x in pos], + pkas_neg=[x[1] for x in neg], + pkas_pos=[x[1] for x in pos], + ) with database: if name in database: @@ -88,10 +102,15 @@ def add(name: Annotated[str, typer.Argument(autocompletion=complete_name_user_de @app.command() -def info(names: Annotated[Optional[List[str]], typer.Argument(help="Component names", autocompletion=complete_name)] = None) -> None: +def info( + names: Annotated[ + Optional[List[str]], + typer.Argument(help="Component names", autocompletion=complete_name), + ] = None +) -> None: """ Show the properties of components. - + If no names are given, print the number of components in the database. """ if names: @@ -107,7 +126,9 @@ def info(names: Annotated[Optional[List[str]], typer.Argument(help="Component na errors_ocurred = True continue - charges = list(range(constituent.pos_count, 0, -1)) + list(range(-1, -constituent.neg_count - 1, -1)) + charges = list(range(constituent.pos_count, 0, -1)) + list( + range(-1, -constituent.neg_count - 1, -1) + ) uu = constituent.u_pos[::-1] + constituent.u_neg[::-1] pkas = constituent.pkas_pos[::-1] + constituent.pkas_neg[::-1] @@ -118,23 +139,33 @@ def info(names: Annotated[Optional[List[str]], typer.Argument(help="Component na typer.echo(f"Component: {name}") if database.is_user_defined(name): typer.echo("[user-defined]") - typer.echo( " " + " ".join(f"{c:^+8d}" for c in charges)) - typer.echo( "Mobilities (*1e-9): " + " ".join(f"{u:^8.2f}" for u in uu)) - typer.echo( "pKas: " + " ".join(f"{p:^8.2f}" for p in pkas)) + typer.echo(" " + " ".join(f"{c:^+8d}" for c in charges)) + typer.echo("Mobilities (*1e-9): " + " ".join(f"{u:^8.2f}" for u in uu)) + typer.echo("pKas: " + " ".join(f"{p:^8.2f}" for p in pkas)) typer.echo(f"Diffusivity: {constituent.diffusivity():.4e}") first = False if errors_ocurred: raise typer.Exit(code=1) - + else: total = len(database) user = len(database.user_defined()) - typer.echo(f"{total} components stored in the database ({total - user} default, {user} user-defined)") + typer.echo( + f"{total} components stored in the database ({total - user} default, {user} user-defined)" + ) + @app.command() -def ls(user: Annotated[Optional[bool], typer.Option("--user/--default", help="List only user-defined/default components")] = None) -> None: +def ls( + user: Annotated[ + Optional[bool], + typer.Option( + "--user/--default", help="List only user-defined/default components" + ), + ] = None +) -> None: """List components in the database.""" if user: @@ -149,8 +180,14 @@ def ls(user: Annotated[Optional[bool], typer.Option("--user/--default", help="Li @app.command() -def rm(names: Annotated[List[str], typer.Argument(autocompletion=complete_name_user_defined)], - force: Annotated[Optional[bool], typer.Option("-f", help="Ignore non-existent components")] = False) -> None: +def rm( + names: Annotated[ + List[str], typer.Argument(autocompletion=complete_name_user_defined) + ], + force: Annotated[ + Optional[bool], typer.Option("-f", help="Ignore non-existent components") + ] = False, +) -> None: """Remove user-defined components from the database.""" errors_ocurred = False for name in names: @@ -170,8 +207,15 @@ def rm(names: Annotated[List[str], typer.Argument(autocompletion=complete_name_u @app.command() -def search(text: str, - user: Annotated[Optional[bool], typer.Option("--user/--default", help="Search only user-defined/default components")] = None) -> None: +def search( + text: str, + user: Annotated[ + Optional[bool], + typer.Option( + "--user/--default", help="Search only user-defined/default components" + ), + ] = None, +) -> None: """Search for a name in the database.""" text = text.upper() @@ -186,7 +230,11 @@ def search(text: str, for name, index in zip(names, match_indices): if index != -1: - typer.echo(name[0:index] + typer.style(name[index:index+len(text)], bold=True) + name[index+len(text):]) + typer.echo( + name[0:index] + + typer.style(name[index : index + len(text)], bold=True) + + name[index + len(text) :] + ) def version_callback(show: bool) -> None: @@ -194,8 +242,16 @@ def version_callback(show: bool) -> None: typer.echo(f"{__package__} {__version__}") raise typer.Exit() + @app.callback() -def common(version: Annotated[bool, typer.Option("--version", help="Show version and exit.", callback=version_callback)] = False) -> None: +def common( + version: Annotated[ + bool, + typer.Option( + "--version", help="Show version and exit.", callback=version_callback + ), + ] = False +) -> None: """Database of electrolytes and their properties.""" pass diff --git a/pyproject.toml b/pyproject.toml index 5078ce1..759cfef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dynamic = ["version"] lint = [ "mypy==1.*", "pytest>=7,<9", + "black" ] test = [ "pytest>=7,<9", diff --git a/tests/test_api.py b/tests/test_api.py index 5114780..b053010 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,6 +3,7 @@ import electrolytes from electrolytes import * + def test_version() -> None: assert isinstance(electrolytes.__version__, str) @@ -30,7 +31,7 @@ def test_get_component() -> None: assert len(c.pkas()) == 6 print(c.mobilities()) print(c.diffusivity()) - assert c.diffusivity() == pytest.approx(28.60*1e-9*8.314*300/96485) + assert c.diffusivity() == pytest.approx(28.60 * 1e-9 * 8.314 * 300 / 96485) def test_known_component_properties() -> None: @@ -48,16 +49,14 @@ def test_try_get_nonexistent() -> None: def test_try_del_nonexistent() -> None: with pytest.raises(KeyError): del database["NONEXISTENT2424612644"] - + def test_try_add_default() -> None: assert "SILVER" in database assert not database.is_user_defined("SILVER") assert "SILVER" not in database.user_defined() with pytest.warns(UserWarning): - database.add(Constituent(name="SILVER", - u_pos=[64.50], - pkas_pos=[11.70])) + database.add(Constituent(name="SILVER", u_pos=[64.50], pkas_pos=[11.70])) assert "SILVER" in database assert not database.is_user_defined("SILVER") - assert "SILVER" not in database.user_defined() \ No newline at end of file + assert "SILVER" not in database.user_defined() diff --git a/tests/test_cli.py b/tests/test_cli.py index 891189e..dad9338 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -9,6 +9,7 @@ runner = CliRunner() + def test_version() -> None: result = runner.invoke(app, ["--version"]) assert result.exit_code == 0 @@ -77,14 +78,12 @@ def test_add_and_rm() -> None: result = runner.invoke(app, ["info", "SILVER", name]) assert result.exit_code != 0 - result = runner.invoke(app, ["add", name.lower(), - "-2", "4", "5"]) + result = runner.invoke(app, ["add", name.lower(), "-2", "4", "5"]) assert result.exit_code != 0 - result = runner.invoke(app, ["add", name.lower(), - "-1", "2", "3", - "-2", "4", "5", - "+1", "6", "-1.5"]) + result = runner.invoke( + app, ["add", name.lower(), "-1", "2", "3", "-2", "4", "5", "+1", "6", "-1.5"] + ) assert result.exit_code == 0 assert name in database @@ -97,15 +96,12 @@ def test_add_and_rm() -> None: assert c.pkas()[2:-1] == pytest.approx([-1.5, 3, 5]) assert c.pkas()[-1] == Constituent._default_pka(-3) - result = runner.invoke(app, ["add", name.upper(), - "-1", "2", "3", - "-2", "4", "5", - "+1", "6", "-1.5"]) + result = runner.invoke( + app, ["add", name.upper(), "-1", "2", "3", "-2", "4", "5", "+1", "6", "-1.5"] + ) assert result.exit_code != 0 - result = runner.invoke(app, ["add", "-f", name, - "+1", "2", "7", - "+2", "4", "5"]) + result = runner.invoke(app, ["add", "-f", name, "+1", "2", "7", "+2", "4", "5"]) assert result.exit_code == 0 assert database[name].pos_count == 2 @@ -147,19 +143,36 @@ def test_extra_charges() -> None: del database[name] except KeyError: pass - + assert name not in database with pytest.raises(KeyError): database[name] - - result = runner.invoke(app, ["add", name, - "+1", "5", "8", - "+2", "7", "6", - "+3", "9", "4", - "+4", "11", "2", - "-1", "1", "10", - "-2", "3", "12"]) + result = runner.invoke( + app, + [ + "add", + name, + "+1", + "5", + "8", + "+2", + "7", + "6", + "+3", + "9", + "4", + "+4", + "11", + "2", + "-1", + "1", + "10", + "-2", + "3", + "12", + ], + ) assert result.exit_code == 0 assert name in database @@ -167,7 +180,9 @@ def test_extra_charges() -> None: assert len(c.mobilities()) == 8 assert len(c.pkas()) == 8 assert c.mobilities() == pytest.approx([11e-9, 9e-9, 7e-9, 5e-9, 1e-9, 3e-9, 0, 0]) - assert c.pkas() == pytest.approx([2, 4, 6, 8, 10, 12, Constituent._default_pka(-3), Constituent._default_pka(-4)]) + assert c.pkas() == pytest.approx( + [2, 4, 6, 8, 10, 12, Constituent._default_pka(-3), Constituent._default_pka(-4)] + ) result = runner.invoke(app, ["info", name]) assert result.exit_code == 0 diff --git a/tests/test_lock.py b/tests/test_lock.py index 3b853a6..0b1c611 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -12,12 +12,10 @@ def test_lock_api_cli() -> None: del database[name] except KeyError: pass - + assert name not in database - database.add(Constituent(name=name, - u_neg=[2.73], - pkas_neg=[3.14])) + database.add(Constituent(name=name, u_neg=[2.73], pkas_neg=[3.14])) with database: proc = subprocess.Popen(["electrolytes", "rm", name])