From f904a4b9c1e85776e9f2fc1baa40f7cb6979a886 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Mon, 25 Mar 2024 11:09:23 -0300 Subject: [PATCH] Refactor internal dictionaries module --- .../__init__.py} | 204 ++---------------- foamlib/_dictionaries/_parsing.py | 56 +++++ foamlib/_dictionaries/_serialization.py | 103 +++++++++ foamlib/_dictionaries/_values.py | 39 ++++ pyproject.toml | 3 - tests/test_dictionaries.py | 42 ++-- 6 files changed, 235 insertions(+), 212 deletions(-) rename foamlib/{_dictionaries.py => _dictionaries/__init__.py} (58%) create mode 100644 foamlib/_dictionaries/_parsing.py create mode 100644 foamlib/_dictionaries/_serialization.py create mode 100644 foamlib/_dictionaries/_values.py diff --git a/foamlib/_dictionaries.py b/foamlib/_dictionaries/__init__.py similarity index 58% rename from foamlib/_dictionaries.py rename to foamlib/_dictionaries/__init__.py index 815c3f4..78d9df5 100644 --- a/foamlib/_dictionaries.py +++ b/foamlib/_dictionaries/__init__.py @@ -9,200 +9,28 @@ MutableMapping, cast, ) -from collections import namedtuple -from dataclasses import dataclass -from contextlib import suppress - -from ._subprocesses import run_process, CalledProcessError +from ._values import FoamDimensionSet, FoamDimensioned, FoamValue +from ._parsing import parse +from ._serialization import serialize +from .._subprocesses import run_process, CalledProcessError try: import numpy as np from numpy.typing import NDArray except ModuleNotFoundError: - numpy = False -else: - numpy = True - -from pyparsing import ( - Forward, - Group, - Keyword, - Literal, - Opt, - QuotedString, - Word, - common, - printables, -) - -FoamDimensionSet = namedtuple( - "FoamDimensionSet", - [ - "mass", - "length", - "time", - "temperature", - "moles", - "current", - "luminous_intensity", - ], - defaults=(0, 0, 0, 0, 0, 0, 0), -) + pass -@dataclass -class FoamDimensioned: - value: Union[int, float, Sequence[Union[int, float]]] = 0 - dimensions: Union[FoamDimensionSet, Sequence[Union[int, float]]] = ( - FoamDimensionSet() - ) - name: Optional[str] = None - - def __post_init__(self) -> None: - if not isinstance(self.dimensions, FoamDimensionSet): - self.dimensions = FoamDimensionSet(*self.dimensions) - - -FoamValue = Union[ - str, int, float, bool, FoamDimensioned, FoamDimensionSet, Sequence["FoamValue"] +__all__ = [ + "FoamDictionary", + "FoamBoundaryDictionary", + "FoamBoundariesDictionary", + "FoamFile", + "FoamFieldFile", + "FoamDimensionSet", + "FoamDimensioned", + "FoamValue", ] -""" -A value that can be stored in an OpenFOAM dictionary. -""" - -_YES = Keyword("yes").set_parse_action(lambda s, loc, tks: True) -_NO = Keyword("no").set_parse_action(lambda s, loc, tks: False) -_DIMENSIONS = ( - Literal("[").suppress() + common.number * 7 + Literal("]").suppress() -).set_parse_action(lambda s, loc, tks: FoamDimensionSet(*tks)) -_TOKEN = common.identifier | QuotedString('"', unquote_results=False) -_ITEM = Forward() -_LIST = Opt( - Literal("List") + Literal("<") + common.identifier + Literal(">") -).suppress() + ( - ( - Opt(common.integer).suppress() - + Literal("(").suppress() - + Group(_ITEM[...]) - + Literal(")").suppress() - ) - | ( - common.integer + Literal("{").suppress() + _ITEM + Literal("}").suppress() - ).set_parse_action(lambda s, loc, tks: [tks[1]] * tks[0]) -) -_FIELD = (Keyword("uniform").suppress() + _ITEM) | ( - Keyword("nonuniform").suppress() + _LIST -) -_DIMENSIONED = (Opt(common.identifier) + _DIMENSIONS + _ITEM).set_parse_action( - lambda s, loc, tks: FoamDimensioned(*reversed(tks.as_list())) -) -_ITEM <<= ( - _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN -) - -_TOKENS = (QuotedString('"', unquote_results=False) | Word(printables))[ - 1, ... -].set_parse_action(lambda s, loc, tks: " ".join(tks)) - -_VALUE = _ITEM ^ _TOKENS - - -def _parse(value: str) -> FoamValue: - return cast(FoamValue, _VALUE.parse_string(value, parse_all=True).as_list()[0]) - - -def _serialize_bool(value: Any) -> str: - if value is True: - return "yes" - elif value is False: - return "no" - else: - raise TypeError(f"Not a bool: {type(value)}") - - -def _is_sequence(value: Any) -> bool: - return ( - isinstance(value, Sequence) - and not isinstance(value, str) - or numpy - and isinstance(value, np.ndarray) - ) - - -def _serialize_list(value: Any) -> str: - if _is_sequence(value): - return f"({' '.join(_serialize(v) for v in value)})" - else: - raise TypeError(f"Not a valid sequence: {type(value)}") - - -def _serialize_field(value: Any) -> str: - if _is_sequence(value): - try: - s = _serialize_list(value) - except TypeError: - raise TypeError(f"Not a valid field: {type(value)}") from None - else: - if len(value) < 10: - return f"uniform {s}" - else: - if isinstance(value[0], (int, float)): - kind = "scalar" - elif len(value[0]) == 3: - kind = "vector" - elif len(value[0]) == 6: - kind = "symmTensor" - elif len(value[0]) == 9: - kind = "tensor" - else: - raise TypeError( - f"Unsupported sequence length for field: {len(value[0])}" - ) - return f"nonuniform List<{kind}> {len(value)}{s}" - else: - return f"uniform {value}" - - -def _serialize_dimensions(value: Any) -> str: - if _is_sequence(value) and len(value) == 7: - return f"[{' '.join(str(v) for v in value)}]" - else: - raise TypeError(f"Not a valid dimension set: {type(value)}") - - -def _serialize_dimensioned(value: Any) -> str: - if isinstance(value, FoamDimensioned): - if value.name is not None: - return f"{value.name} {_serialize_dimensions(value.dimensions)} {_serialize(value.value)}" - else: - return ( - f"{_serialize_dimensions(value.dimensions)} {_serialize(value.value)}" - ) - else: - raise TypeError(f"Not a valid dimensioned value: {type(value)}") - - -def _serialize( - value: Any, *, assume_field: bool = False, assume_dimensions: bool = False -) -> str: - if isinstance(value, FoamDimensionSet) or assume_dimensions: - with suppress(TypeError): - return _serialize_dimensions(value) - - if assume_field: - with suppress(TypeError): - return _serialize_field(value) - - with suppress(TypeError): - return _serialize_dimensioned(value) - - with suppress(TypeError): - return _serialize_list(value) - - with suppress(TypeError): - return _serialize_bool(value) - - return str(value) class FoamDictionary(MutableMapping[str, Union[FoamValue, "FoamDictionary"]]): @@ -245,7 +73,7 @@ def __getitem__(self, key: str) -> Union[FoamValue, "FoamDictionary"]: assert value.endswith("}") return FoamDictionary(self._file, [*self._keywords, key]) else: - return _parse(value) + return parse(value) def _setitem( self, @@ -265,7 +93,7 @@ def _setitem( subdict[k] = v return else: - value = _serialize( + value = serialize( value, assume_field=assume_field, assume_dimensions=assume_dimensions ) diff --git a/foamlib/_dictionaries/_parsing.py b/foamlib/_dictionaries/_parsing.py new file mode 100644 index 0000000..48a99cd --- /dev/null +++ b/foamlib/_dictionaries/_parsing.py @@ -0,0 +1,56 @@ +from typing import cast + +from pyparsing import ( + Forward, + Group, + Keyword, + Literal, + Opt, + QuotedString, + Word, + common, + printables, +) + +from ._values import FoamValue, FoamDimensionSet, FoamDimensioned + + +_YES = Keyword("yes").set_parse_action(lambda s, loc, tks: True) +_NO = Keyword("no").set_parse_action(lambda s, loc, tks: False) +_DIMENSIONS = ( + Literal("[").suppress() + common.number * 7 + Literal("]").suppress() +).set_parse_action(lambda s, loc, tks: FoamDimensionSet(*tks)) +_TOKEN = common.identifier | QuotedString('"', unquote_results=False) +_ITEM = Forward() +_LIST = Opt( + Literal("List") + Literal("<") + common.identifier + Literal(">") +).suppress() + ( + ( + Opt(common.integer).suppress() + + Literal("(").suppress() + + Group(_ITEM[...]) + + Literal(")").suppress() + ) + | ( + common.integer + Literal("{").suppress() + _ITEM + Literal("}").suppress() + ).set_parse_action(lambda s, loc, tks: [tks[1]] * tks[0]) +) +_FIELD = (Keyword("uniform").suppress() + _ITEM) | ( + Keyword("nonuniform").suppress() + _LIST +) +_DIMENSIONED = (Opt(common.identifier) + _DIMENSIONS + _ITEM).set_parse_action( + lambda s, loc, tks: FoamDimensioned(*reversed(tks.as_list())) +) +_ITEM <<= ( + _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN +) + +_TOKENS = (QuotedString('"', unquote_results=False) | Word(printables))[ + 1, ... +].set_parse_action(lambda s, loc, tks: " ".join(tks)) + +_VALUE = _ITEM ^ _TOKENS + + +def parse(value: str) -> FoamValue: + return cast(FoamValue, _VALUE.parse_string(value, parse_all=True).as_list()[0]) diff --git a/foamlib/_dictionaries/_serialization.py b/foamlib/_dictionaries/_serialization.py new file mode 100644 index 0000000..fc9cef6 --- /dev/null +++ b/foamlib/_dictionaries/_serialization.py @@ -0,0 +1,103 @@ +from contextlib import suppress +from typing import Any, Sequence + +try: + import numpy as np +except ModuleNotFoundError: + numpy = False +else: + numpy = True + +from ._values import FoamDimensioned, FoamDimensionSet + + +def _serialize_bool(value: Any) -> str: + if value is True: + return "yes" + elif value is False: + return "no" + else: + raise TypeError(f"Not a bool: {type(value)}") + + +def _is_sequence(value: Any) -> bool: + return ( + isinstance(value, Sequence) + and not isinstance(value, str) + or numpy + and isinstance(value, np.ndarray) + ) + + +def _serialize_list(value: Any) -> str: + if _is_sequence(value): + return f"({' '.join(serialize(v) for v in value)})" + else: + raise TypeError(f"Not a valid sequence: {type(value)}") + + +def _serialize_field(value: Any) -> str: + if _is_sequence(value): + try: + s = _serialize_list(value) + except TypeError: + raise TypeError(f"Not a valid field: {type(value)}") from None + else: + if len(value) < 10: + return f"uniform {s}" + else: + if isinstance(value[0], (int, float)): + kind = "scalar" + elif len(value[0]) == 3: + kind = "vector" + elif len(value[0]) == 6: + kind = "symmTensor" + elif len(value[0]) == 9: + kind = "tensor" + else: + raise TypeError( + f"Unsupported sequence length for field: {len(value[0])}" + ) + return f"nonuniform List<{kind}> {len(value)}{s}" + else: + return f"uniform {value}" + + +def _serialize_dimensions(value: Any) -> str: + if _is_sequence(value) and len(value) == 7: + return f"[{' '.join(str(v) for v in value)}]" + else: + raise TypeError(f"Not a valid dimension set: {type(value)}") + + +def _serialize_dimensioned(value: Any) -> str: + if isinstance(value, FoamDimensioned): + if value.name is not None: + return f"{value.name} {_serialize_dimensions(value.dimensions)} {serialize(value.value)}" + else: + return f"{_serialize_dimensions(value.dimensions)} {serialize(value.value)}" + else: + raise TypeError(f"Not a valid dimensioned value: {type(value)}") + + +def serialize( + value: Any, *, assume_field: bool = False, assume_dimensions: bool = False +) -> str: + if isinstance(value, FoamDimensionSet) or assume_dimensions: + with suppress(TypeError): + return _serialize_dimensions(value) + + if assume_field: + with suppress(TypeError): + return _serialize_field(value) + + with suppress(TypeError): + return _serialize_dimensioned(value) + + with suppress(TypeError): + return _serialize_list(value) + + with suppress(TypeError): + return _serialize_bool(value) + + return str(value) diff --git a/foamlib/_dictionaries/_values.py b/foamlib/_dictionaries/_values.py new file mode 100644 index 0000000..81e2f1f --- /dev/null +++ b/foamlib/_dictionaries/_values.py @@ -0,0 +1,39 @@ +from collections import namedtuple +from dataclasses import dataclass +from typing import Optional, Sequence, Union + + +FoamDimensionSet = namedtuple( + "FoamDimensionSet", + [ + "mass", + "length", + "time", + "temperature", + "moles", + "current", + "luminous_intensity", + ], + defaults=(0, 0, 0, 0, 0, 0, 0), +) + + +@dataclass +class FoamDimensioned: + value: Union[int, float, Sequence[Union[int, float]]] = 0 + dimensions: Union[FoamDimensionSet, Sequence[Union[int, float]]] = ( + FoamDimensionSet() + ) + name: Optional[str] = None + + def __post_init__(self) -> None: + if not isinstance(self.dimensions, FoamDimensionSet): + self.dimensions = FoamDimensionSet(*self.dimensions) + + +FoamValue = Union[ + str, int, float, bool, FoamDimensioned, FoamDimensionSet, Sequence["FoamValue"] +] +""" +A value that can be stored in an OpenFOAM dictionary. +""" diff --git a/pyproject.toml b/pyproject.toml index 2456e9c..4d98265 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,9 +61,6 @@ Homepage = "https://github.com/gerlero/foamlib" Repository = "https://github.com/gerlero/foamlib" Documentation = "https://foamlib.readthedocs.io" -[tool.setuptools] -packages = ["foamlib"] - [tool.setuptools.dynamic] version = {attr = "foamlib.__version__"} diff --git a/tests/test_dictionaries.py b/tests/test_dictionaries.py index 747f2ae..2a3eeac 100644 --- a/tests/test_dictionaries.py +++ b/tests/test_dictionaries.py @@ -7,40 +7,40 @@ import numpy as np from foamlib import * -from foamlib._dictionaries import _parse +from foamlib._dictionaries._parsing import parse def test_parse() -> None: - assert _parse("1") == 1 - assert _parse("1.0") == 1.0 - assert _parse("1.0e-3") == 1.0e-3 - assert _parse("yes") is True - assert _parse("no") is False - assert _parse("word") == "word" - assert _parse("word word") == "word word" - assert _parse('"a string"') == '"a string"' - assert _parse("uniform 1") == 1 - assert _parse("uniform 1.0") == 1.0 - assert _parse("uniform 1.0e-3") == 1.0e-3 - assert _parse("(1.0 2.0 3.0)") == [1.0, 2.0, 3.0] - assert _parse("nonuniform List 2(1 2)") == [1, 2] - assert _parse("3(1 2 3)") == [1, 2, 3] - assert _parse("2((1 2 3) (4 5 6))") == [[1, 2, 3], [4, 5, 6]] - assert _parse("nonuniform List 2((1 2 3) (4 5 6))") == [ + assert parse("1") == 1 + assert parse("1.0") == 1.0 + assert parse("1.0e-3") == 1.0e-3 + assert parse("yes") is True + assert parse("no") is False + assert parse("word") == "word" + assert parse("word word") == "word word" + assert parse('"a string"') == '"a string"' + assert parse("uniform 1") == 1 + assert parse("uniform 1.0") == 1.0 + assert parse("uniform 1.0e-3") == 1.0e-3 + assert parse("(1.0 2.0 3.0)") == [1.0, 2.0, 3.0] + assert parse("nonuniform List 2(1 2)") == [1, 2] + assert parse("3(1 2 3)") == [1, 2, 3] + assert parse("2((1 2 3) (4 5 6))") == [[1, 2, 3], [4, 5, 6]] + assert parse("nonuniform List 2((1 2 3) (4 5 6))") == [ [1, 2, 3], [4, 5, 6], ] - assert _parse("[1 1 -2 0 0 0 0]") == FoamDimensionSet(mass=1, length=1, time=-2) - assert _parse("g [1 1 -2 0 0 0 0] (0 0 -9.81)") == FoamDimensioned( + assert parse("[1 1 -2 0 0 0 0]") == FoamDimensionSet(mass=1, length=1, time=-2) + assert parse("g [1 1 -2 0 0 0 0] (0 0 -9.81)") == FoamDimensioned( name="g", dimensions=FoamDimensionSet(mass=1, length=1, time=-2), value=[0, 0, -9.81], ) - assert _parse("[1 1 -2 0 0 0 0] 9.81") == FoamDimensioned( + assert parse("[1 1 -2 0 0 0 0] 9.81") == FoamDimensioned( dimensions=FoamDimensionSet(mass=1, length=1, time=-2), value=9.81 ) assert ( - _parse("hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)") + parse("hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)") == "hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)" )