diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 15a12ec..420c640 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -7,13 +7,16 @@ jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: master - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -28,6 +31,3 @@ jobs: - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_api_key }} diff --git a/.github/workflows/run_tox.yml b/.github/workflows/run_tox.yml index 653dbbb..abda4eb 100644 --- a/.github/workflows/run_tox.yml +++ b/.github/workflows/run_tox.yml @@ -7,8 +7,8 @@ jobs: name: pre-commit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - uses: pre-commit/action@v3.0.0 @@ -23,9 +23,9 @@ jobs: python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 52c7914..7f3d81a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,26 +6,18 @@ repos: - id: check-builtin-literals - id: check-docstring-first - id: check-merge-conflict - # - id: check-toml + - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort (python) - - repo: https://github.com/PyCQA/flake8 - rev: '6.1.0' + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==23.9.16 - - flake8-comprehensions==3.14 - - flake8-noqa==1.3 + - id: ruff + - repo: meta hooks: diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..3e05d74 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,66 @@ + +line-length = 120 +indent-width = 4 + +target-version = "py38" +src = ["src", "test"] + +[lint] + +# https://docs.astral.sh/ruff/settings/#ignore-init-module-imports +ignore-init-module-imports = true + +select = [ + "E", "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "I", # https://docs.astral.sh/ruff/rules/#isort-i + "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up + + "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + "ASYNC", # https://docs.astral.sh/ruff/rules/#flake8-async-async + "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "EM", # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + "FIX", # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + "INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + "RET", # https://docs.astral.sh/ruff/rules/#flake8-return-ret + "SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "SLOT", # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + "T10", # https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 + "TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + "TD", # https://docs.astral.sh/ruff/rules/#flake8-todos-td + + "TRY", # https://docs.astral.sh/ruff/rules/#tryceratops-try + "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly + "PERF", # https://docs.astral.sh/ruff/rules/#perflint-perf + "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + + # "PL", # https://docs.astral.sh/ruff/rules/#pylint-pl + # "FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb +] + +ignore = [ + "PT007" # Wrong values type in `@pytest.mark.parametrize` expected `list` of `tuple` +] + + +[format] +# Use single quotes for non-triple-quoted strings. +quote-style = "single" + + +# [lint.flake8-builtins] +# builtins-ignorelist = ["id", "input"] + + +[lint.per-file-ignores] +"src/smllib/errors.py" = ["A002"] # Argument `type` is shadowing a Python builtin +"src/smllib/sml/_field_info.py" = ["A002"] # Argument `type` is shadowing a Python builtin +"tests/*" = ["INP001"] # INP001 File `PATH` is part of an implicit namespace package. Add an `__init__.py`. + + +[lint.isort] +# https://docs.astral.sh/ruff/settings/#lint_isort_lines-after-imports +lines-after-imports = 2 diff --git a/requirements.txt b/requirements.txt index 149ad68..cfed223 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,15 @@ -pytest == 7.4.2 -pre-commit == 3.5.0 +# ----------------------------------------------------------------------------- +# Packages required for testing +# ----------------------------------------------------------------------------- +-r requirements_tests.txt + +# ----------------------------------------------------------------------------- +# Packages for source formatting +# ----------------------------------------------------------------------------- +ruff == 0.3.4 +pre-commit == 3.7.0 + +# ----------------------------------------------------------------------------- +# Packages for other developement tasks +# ----------------------------------------------------------------------------- +pur == 7.3.1 # Update requirements.txt diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 0000000..2dd06ab --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1 @@ +pytest == 8.1.1 diff --git a/setup.py b/setup.py index 472eddb..c49c2dd 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ import typing from pathlib import Path -import setuptools # type: ignore +import setuptools def load_version() -> str: version: typing.Dict[str, str] = {} - with open("src/smllib/__version__.py") as fp: + with (Path(__file__).parent / "src/smllib/__version__.py").open() as fp: exec(fp.read(), version) assert version['__version__'], version return version['__version__'] diff --git a/src/smllib/__init__.py b/src/smllib/__init__.py index 642287a..c6894c3 100644 --- a/src/smllib/__init__.py +++ b/src/smllib/__init__.py @@ -1,6 +1,7 @@ from smllib import errors from smllib.__version__ import __version__ + # isort: split import smllib.reader diff --git a/src/smllib/__version__.py b/src/smllib/__version__.py index 6f4fa58..0f66308 100644 --- a/src/smllib/__version__.py +++ b/src/smllib/__version__.py @@ -1 +1 @@ -__version__ = '1.3' +__version__ = '1.4' diff --git a/src/smllib/builder/__init__.py b/src/smllib/builder/__init__.py index 61376c9..7f04f7a 100644 --- a/src/smllib/builder/__init__.py +++ b/src/smllib/builder/__init__.py @@ -1,12 +1,16 @@ -from ._builder import SmlObjBuilder, T_SML_BUILDER +from ._builder import T_SML_BUILDER, SmlObjBuilder + # isort: split + from .list_entry import SmlListEntryBuilder from .message import SmlMessageBuilder from .response_get_list import SmlGetListResponseBuilder from .response_open_close import SmlCloseResponseBuilder, SmlOpenResponseBuilder + # isort: split -from .default_context import create_context, CTX_HINT + +from .default_context import CTX_HINT, create_context diff --git a/src/smllib/builder/_builder.py b/src/smllib/builder/_builder.py index 7cebca8..223f08c 100644 --- a/src/smllib/builder/_builder.py +++ b/src/smllib/builder/_builder.py @@ -1,7 +1,7 @@ from typing import Dict, Generic, Type, TypeVar from smllib.errors import WrongArgCount, WrongValueType -from smllib.sml import inspect_obj, SmlBaseObj, SmlObjFieldInfo, T_SML_OBJ +from smllib.sml import T_SML_OBJ, SmlBaseObj, SmlObjFieldInfo, inspect_obj from smllib.sml_frame_snippet import SmlFrameSnippet @@ -37,7 +37,8 @@ def build(self, obj: SmlFrameSnippet, classes: Dict[Type[SmlBaseObj], 'SmlObjBui value = tuple([cls_builder.build(v, classes) for v in value]) else: if not isinstance(value, field.type): - raise WrongValueType(f'{value} ({type(value)}) != {field.type}') + msg = f'{value} ({type(value)}) != {field.type}' + raise WrongValueType(msg) setattr(out, name, value) return out diff --git a/src/smllib/builder/default_context.py b/src/smllib/builder/default_context.py index 517133c..3efd6f0 100644 --- a/src/smllib/builder/default_context.py +++ b/src/smllib/builder/default_context.py @@ -2,8 +2,15 @@ from smllib.sml import SmlBaseObj, SmlCloseResponse, SmlGetListResponse, SmlListEntry, SmlMessage, SmlOpenResponse -from . import SmlCloseResponseBuilder, SmlGetListResponseBuilder, \ - SmlListEntryBuilder, SmlMessageBuilder, SmlOpenResponseBuilder, T_SML_BUILDER +from . import ( + T_SML_BUILDER, + SmlCloseResponseBuilder, + SmlGetListResponseBuilder, + SmlListEntryBuilder, + SmlMessageBuilder, + SmlOpenResponseBuilder, +) + CTX_HINT = Dict[Type[SmlBaseObj], T_SML_BUILDER] diff --git a/src/smllib/builder/list_entry.py b/src/smllib/builder/list_entry.py index a4d358a..1cc1028 100644 --- a/src/smllib/builder/list_entry.py +++ b/src/smllib/builder/list_entry.py @@ -15,7 +15,8 @@ def build(self, obj: SmlFrameSnippet, classes: Dict[Type[SmlBaseObj], 'SmlObjBui value = ret.value if value is None: - raise ValueError('value is required!') + msg = 'value is required!' + raise ValueError(msg) # Maybe it's ascii so we try to decode it if isinstance(value, str): diff --git a/src/smllib/crc.py b/src/smllib/crc.py index 62083da..5f7b1f5 100644 --- a/src/smllib/crc.py +++ b/src/smllib/crc.py @@ -1,5 +1,6 @@ from typing import Union + CRC16_X25_TABLE = ( 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, diff --git a/src/smllib/reader.py b/src/smllib/reader.py index 1c16365..5de3b60 100644 --- a/src/smllib/reader.py +++ b/src/smllib/reader.py @@ -1,6 +1,6 @@ from typing import Optional -from smllib.builder import create_context, CTX_HINT +from smllib.builder import CTX_HINT, create_context from smllib.crc import get_crc from smllib.errors import CrcError from smllib.sml_frame import SmlFrame diff --git a/src/smllib/sml/__init__.py b/src/smllib/sml/__init__.py index a9a5985..ddb2a7b 100644 --- a/src/smllib/sml/__init__.py +++ b/src/smllib/sml/__init__.py @@ -1,19 +1,29 @@ -from smllib.sml._base_obj import SmlBaseObj, T_SML_OBJ +from smllib.sml._base_obj import T_SML_OBJ, SmlBaseObj from smllib.sml.sml_eom import EndOfSmlMsg + # isort: split + from smllib.sml.sml_choice import SmlChoice + # isort: split -from smllib.sml._field_info import inspect_obj, SmlObjFieldInfo + + +from smllib.sml._field_info import SmlObjFieldInfo, inspect_obj + # isort: split + from smllib.sml.list_entry import SmlListEntry from smllib.sml.response_get_list import SmlGetListResponse from smllib.sml.response_open_close import SmlCloseResponse, SmlOpenResponse from smllib.sml.sml_obis import ObisCode + # isort: split + + from smllib.sml.message import SmlMessage diff --git a/src/smllib/sml/_base_obj.py b/src/smllib/sml/_base_obj.py index bcb350d..274c3f6 100644 --- a/src/smllib/sml/_base_obj.py +++ b/src/smllib/sml/_base_obj.py @@ -1,10 +1,12 @@ +import typing from typing import Final, TypeVar + INDENT: Final = ' ' class SmlBaseObj: - __sml__: dict + __sml__: typing.ClassVar[dict] def format_msg(self, indent: int = 0): indent += 1 @@ -12,13 +14,13 @@ def format_msg(self, indent: int = 0): w = max(map(len, self.__dict__), default=0) for k, v in self.__dict__.items(): if isinstance(v, SmlBaseObj): - r += f'{INDENT * indent}{str(k):s} {v.format_msg(indent)}' + r += f'{INDENT * indent}{k!s:s} {v.format_msg(indent)}' elif isinstance(v, (tuple, list)): - r += f'{INDENT * indent}{str(k):s}:\n' + r += f'{INDENT * indent}{k!s:s}:\n' for e in v: r += f'{INDENT * (indent + 1)}{e.format_msg(indent + 1)}' else: - r += f'{INDENT * indent}{str(k):{w}s}: {v}\n' + r += f'{INDENT * indent}{k!s:{w}s}: {v}\n' return r diff --git a/src/smllib/sml/_field_info.py b/src/smllib/sml/_field_info.py index 321ad69..78e67a0 100644 --- a/src/smllib/sml/_field_info.py +++ b/src/smllib/sml/_field_info.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Final, get_args, get_origin, get_type_hints, Optional, Type, Union +from typing import Any, Callable, Dict, Final, Optional, Type, Union, get_args, get_origin, get_type_hints from smllib.sml import SmlBaseObj, SmlChoice @@ -64,6 +64,7 @@ def inspect_obj(cls: Type['SmlBaseObj']) -> Dict[str, SmlObjFieldInfo]: info.type = args[0] continue - raise ValueError(f'Unknown hint: {origin} for {name}') + msg = f'Unknown hint: {origin} for {name}' + raise ValueError(msg) return fields diff --git a/src/smllib/sml/list_entry.py b/src/smllib/sml/list_entry.py index 0a300c4..baa52bb 100644 --- a/src/smllib/sml/list_entry.py +++ b/src/smllib/sml/list_entry.py @@ -1,15 +1,15 @@ -from typing import Dict, Optional, Union +from typing import ClassVar, Dict, Optional, Union from smllib.const import OBIS_NAMES, UNITS from smllib.sml import SmlObjFieldInfo from smllib.sml._base_obj import INDENT, SmlBaseObj -from .sml_obis import build_obis, ObisCode -from .sml_time import build_time, TIME_HINT +from .sml_obis import ObisCode, build_obis +from .sml_time import TIME_HINT, build_time class SmlListEntry(SmlBaseObj): - __sml__: Dict[str, SmlObjFieldInfo] = { + __sml__: ClassVar[Dict[str, SmlObjFieldInfo]] = { 'val_time': SmlObjFieldInfo(func=build_time), 'obis': SmlObjFieldInfo(func=build_obis) } @@ -35,7 +35,7 @@ def format_msg(self, indent: int = 0): w = max(map(len, self.__dict__), default=0) for k, v in self.__dict__.items(): - r += f'{INDENT*indent}{str(k):{w}s}: {v}{f" ({self.obis.obis_code})" if k == "obis" else ""}\n' + r += f'{INDENT*indent}{k!s:{w}s}: {v}{f" ({self.obis.obis_code})" if k == "obis" else ""}\n' summary = '' if self.unit: diff --git a/src/smllib/sml/message.py b/src/smllib/sml/message.py index e173ed3..3d306e8 100644 --- a/src/smllib/sml/message.py +++ b/src/smllib/sml/message.py @@ -1,7 +1,8 @@ -from typing import Dict, Type, Union +from typing import ClassVar, Dict, Type, Union from smllib.sml import SmlBaseObj, SmlChoice, SmlCloseResponse, SmlGetListResponse, SmlObjFieldInfo, SmlOpenResponse + MSG_TYPES: Dict[int, Type[SmlBaseObj]] = { # 0x0100: 'SmlOpenRequest', 0x0101: SmlOpenResponse, @@ -22,7 +23,7 @@ class SmlMessage(SmlBaseObj): - __sml__: Dict[str, SmlObjFieldInfo] = { + __sml__: ClassVar[Dict[str, SmlObjFieldInfo]] = { 'message_body': SmlObjFieldInfo(choice=SmlChoice(MSG_TYPES)) } diff --git a/src/smllib/sml/response_get_list.py b/src/smllib/sml/response_get_list.py index b388da5..7d812cd 100644 --- a/src/smllib/sml/response_get_list.py +++ b/src/smllib/sml/response_get_list.py @@ -1,12 +1,12 @@ -from typing import Dict, Optional, Tuple +from typing import ClassVar, Dict, Optional, Tuple from smllib.sml import SmlBaseObj, SmlListEntry, SmlObjFieldInfo -from .sml_time import build_time, TIME_HINT +from .sml_time import TIME_HINT, build_time class SmlGetListResponse(SmlBaseObj): - __sml__: Dict[str, SmlObjFieldInfo] = { + __sml__: ClassVar[Dict[str, SmlObjFieldInfo]] = { 'act_sensor_time': SmlObjFieldInfo(func=build_time), 'act_gateway_time': SmlObjFieldInfo(func=build_time), } diff --git a/src/smllib/sml/response_open_close.py b/src/smllib/sml/response_open_close.py index 9be70cd..078b392 100644 --- a/src/smllib/sml/response_open_close.py +++ b/src/smllib/sml/response_open_close.py @@ -1,12 +1,12 @@ -from typing import Dict, Optional +from typing import ClassVar, Dict, Optional from smllib.sml import SmlBaseObj, SmlObjFieldInfo -from .sml_time import build_time, TIME_HINT +from .sml_time import TIME_HINT, build_time class SmlOpenResponse(SmlBaseObj): - __sml__: Dict[str, SmlObjFieldInfo] = { + __sml__: ClassVar[Dict[str, SmlObjFieldInfo]] = { 'ref_time': SmlObjFieldInfo(func=build_time) } @@ -19,6 +19,6 @@ class SmlOpenResponse(SmlBaseObj): class SmlCloseResponse(SmlBaseObj): - __sml__: Dict[str, SmlObjFieldInfo] = {} + __sml__: ClassVar[Dict[str, SmlObjFieldInfo]] = {} global_signature: Optional[str] diff --git a/src/smllib/sml/sml_obis.py b/src/smllib/sml/sml_obis.py index 5794c83..f832fcf 100644 --- a/src/smllib/sml/sml_obis.py +++ b/src/smllib/sml/sml_obis.py @@ -1,4 +1,6 @@ class ObisCode(str): + __slots__ = ('obis_code', 'obis_short') + def __init__(self, obis_hex: str): _a = int(obis_hex[0:2], 16) _b = int(obis_hex[2:4], 16) @@ -10,12 +12,12 @@ def __init__(self, obis_hex: str): self.obis_short = f'{_c}.{_d}.{_e}' def __new__(cls, obis_hex: str): - obj = str.__new__(cls, obis_hex) - return obj + return str.__new__(cls, obis_hex) def build_obis(_in) -> ObisCode: if not isinstance(_in, str) or len(_in) != 12: - raise ValueError(f'Obis code must be a 12 char hex str (is "{_in}" {type(_in)})!') + msg = f'Obis code must be a 12 char hex str (is "{_in}" {type(_in)})!' + raise ValueError(msg) return ObisCode(_in) diff --git a/src/smllib/sml/sml_time.py b/src/smllib/sml/sml_time.py index d1a6090..d2d1e9f 100644 --- a/src/smllib/sml/sml_time.py +++ b/src/smllib/sml/sml_time.py @@ -3,6 +3,7 @@ from smllib.errors import UnsupportedChoiceValue + TIME_HINT = Union[None, int, datetime] diff --git a/src/smllib/sml_frame.py b/src/smllib/sml_frame.py index 63df94b..bdb820e 100644 --- a/src/smllib/sml_frame.py +++ b/src/smllib/sml_frame.py @@ -1,6 +1,6 @@ from typing import List, Optional -from smllib.builder import create_context, CTX_HINT +from smllib.builder import CTX_HINT, create_context from smllib.errors import InvalidBufferPos from smllib.sml import EndOfSmlMsg, SmlListEntry, SmlMessage from smllib.sml_frame_snippet import SmlFrameSnippet @@ -24,7 +24,8 @@ def get_value(self, pos: Optional[int] = None) -> SmlFrameSnippet: # check start pos if pos >= self.buf_len: - raise InvalidBufferPos(f'Start pos bigger than buffer: {pos} > {self.buf_len}') + msg = f'Start pos bigger than buffer: {pos} > {self.buf_len}' + raise InvalidBufferPos(msg) # advance v = self.buffer[pos] @@ -69,7 +70,8 @@ def get_value(self, pos: Optional[int] = None) -> SmlFrameSnippet: # End position end = pos + _size if end > self.buf_len: - raise InvalidBufferPos(f'Pos bigger than buffer: {end} > {self.buf_len}') + msg = f'Pos bigger than buffer: {end} > {self.buf_len}' + raise InvalidBufferPos(msg) self.next_pos = end # 0x50: signed integer, 0x60 unsigned integer @@ -83,18 +85,20 @@ def get_value(self, pos: Optional[int] = None) -> SmlFrameSnippet: if _type == 0x00: return SmlFrameSnippet(self.buffer[start:end].hex(), snip_start, end, self.buffer) - raise ValueError(f'Unknown data type: {_type:02x}!') + msg = f'Unknown data type: {_type:02x}!' + raise ValueError(msg) def parse_frame(self) -> List[SmlMessage]: ret = [] self.next_pos = 0 while self.next_pos < self.buf_len: - if not self.buffer[self.next_pos] == 0x76: - raise ValueError( + if self.buffer[self.next_pos] != 0x76: + msg = ( f'No start of SML Message found at {self.next_pos}: 0x{self.buffer[self.next_pos]:x}\n' f'{self.buffer.hex()}' ) + raise ValueError(msg) # This will always return a list val = self._parse_msg(self.get_value()) diff --git a/src/smllib/sml_frame_snippet.py b/src/smllib/sml_frame_snippet.py index 78cc67d..fdf9a8f 100644 --- a/src/smllib/sml_frame_snippet.py +++ b/src/smllib/sml_frame_snippet.py @@ -3,6 +3,7 @@ from smllib.errors import WrongValueType from smllib.sml.sml_eom import CEndOfSmlMsg + SNIP_TYPE = TypeVar('SNIP_TYPE', bound=object) @@ -15,7 +16,8 @@ def __init__(self, value: Union[None, bool, int, str, float, list, CEndOfSmlMsg] msg = None if stop is not None: if buf is None: - raise ValueError('Arg stop und buf must be used together') + err_msg = 'Arg stop und buf must be used together' + raise ValueError(err_msg) msg = buf[start: stop] self.value = value @@ -30,5 +32,6 @@ def stop_pos(self, pos: int, buf: memoryview) -> 'SmlFrameSnippet': def get_value(self, val_type: Type[SNIP_TYPE]) -> SNIP_TYPE: value = self.value if not isinstance(value, val_type): - raise WrongValueType(f'Expected type {val_type.__name__} but got type {type(value).__name__}') + msg = f'Expected type {val_type.__name__} but got type {type(value).__name__}' + raise WrongValueType(msg) return value diff --git a/tests/builder/test_build.py b/tests/builder/test_build.py index 0cec764..39ff9c5 100644 --- a/tests/builder/test_build.py +++ b/tests/builder/test_build.py @@ -1,8 +1,14 @@ -from smllib.builder import SmlCloseResponseBuilder, SmlGetListResponseBuilder, \ - SmlListEntryBuilder, SmlMessageBuilder, SmlObjBuilder -from smllib.sml import EndOfSmlMsg, SmlCloseResponse, SmlListEntry from tests.helper import in_snip +from smllib.builder import ( + SmlCloseResponseBuilder, + SmlGetListResponseBuilder, + SmlListEntryBuilder, + SmlMessageBuilder, + SmlObjBuilder, +) +from smllib.sml import EndOfSmlMsg, SmlCloseResponse, SmlListEntry + def test_build_entry(): builder = SmlListEntryBuilder() diff --git a/tests/builder/test_inspect.py b/tests/builder/test_inspect.py index 5dcce3e..650e834 100644 --- a/tests/builder/test_inspect.py +++ b/tests/builder/test_inspect.py @@ -1,9 +1,16 @@ from datetime import datetime -from smllib.sml import inspect_obj, SmlChoice, SmlCloseResponse, \ - SmlGetListResponse, SmlListEntry, SmlObjFieldInfo, SmlOpenResponse +from smllib.sml import ( + SmlChoice, + SmlCloseResponse, + SmlGetListResponse, + SmlListEntry, + SmlObjFieldInfo, + SmlOpenResponse, + inspect_obj, +) from smllib.sml.message import MSG_TYPES, SmlMessage -from smllib.sml.sml_obis import build_obis, ObisCode +from smllib.sml.sml_obis import ObisCode, build_obis from smllib.sml.sml_time import build_time diff --git a/tests/conftest.py b/tests/conftest.py index bbd8045..1d1e930 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import sys from pathlib import Path + # add src dir so tests work src = Path(__file__).parent.with_name('src') assert src.is_dir(), src diff --git a/tests/sml/test_format.py b/tests/sml/test_format.py index 59fd9e9..480d33d 100644 --- a/tests/sml/test_format.py +++ b/tests/sml/test_format.py @@ -1,24 +1,33 @@ -from smllib.builder import SmlCloseResponseBuilder, \ - SmlGetListResponseBuilder, SmlListEntryBuilder, SmlOpenResponseBuilder -from smllib.sml import SmlListEntry from tests.helper import in_snip +from smllib.builder import ( + SmlCloseResponseBuilder, + SmlGetListResponseBuilder, + SmlListEntryBuilder, + SmlOpenResponseBuilder, +) +from smllib.sml import SmlListEntry + def test_open_response(): r = SmlOpenResponseBuilder().build(in_snip([None, None, 'ab', 'cd', None, 1]), {}) - assert r.format_msg() == '\n' \ - ' codepage : None\n' \ - ' client_id : None\n' \ - ' req_file_id: ab\n' \ - ' server_id : cd\n' \ - ' ref_time : None\n' \ - ' sml_version: 1\n' + assert r.format_msg() == ( + '\n' + ' codepage : None\n' + ' client_id : None\n' + ' req_file_id: ab\n' + ' server_id : cd\n' + ' ref_time : None\n' + ' sml_version: 1\n' + ) def test_close_response(): r = SmlCloseResponseBuilder().build(in_snip(['my_sig']), {}) - assert r.format_msg() == '\n' \ - ' global_signature: my_sig\n' + assert r.format_msg() == ( + '\n' + ' global_signature: my_sig\n' + ) def test_list_entry(): @@ -32,29 +41,31 @@ def test_list_entry(): builder = SmlGetListResponseBuilder() obj = builder.build(data, {SmlListEntry: SmlListEntryBuilder()}) - assert obj.format_msg() == '\n' \ - ' client_id : None\n' \ - ' server_id : server\n' \ - ' list_name : None\n' \ - ' act_sensor_time : None\n' \ - ' val_list:\n' \ - ' \n' \ - ' obis : 0100010800ff (1-0:1.8.0*255)\n' \ - ' status : None\n' \ - ' val_time : None\n' \ - ' unit : None\n' \ - ' scaler : None\n' \ - ' value : val1\n' \ - ' value_signature: None\n' \ - ' -> (Zählerstand Bezug Total)\n' \ - ' \n' \ - ' obis : 0100010801ff (1-0:1.8.1*255)\n' \ - ' status : None\n' \ - ' val_time : None\n' \ - ' unit : None\n' \ - ' scaler : None\n' \ - ' value : val2\n' \ - ' value_signature: None\n' \ - ' -> (Zählerstand Bezug Tarif 1)\n' \ - ' list_signature : None\n' \ - ' act_gateway_time: None\n' + assert obj.format_msg() == ( + '\n' + ' client_id : None\n' + ' server_id : server\n' + ' list_name : None\n' + ' act_sensor_time : None\n' + ' val_list:\n' + ' \n' + ' obis : 0100010800ff (1-0:1.8.0*255)\n' + ' status : None\n' + ' val_time : None\n' + ' unit : None\n' + ' scaler : None\n' + ' value : val1\n' + ' value_signature: None\n' + ' -> (Zählerstand Bezug Total)\n' + ' \n' + ' obis : 0100010801ff (1-0:1.8.1*255)\n' + ' status : None\n' + ' val_time : None\n' + ' unit : None\n' + ' scaler : None\n' + ' value : val2\n' + ' value_signature: None\n' + ' -> (Zählerstand Bezug Tarif 1)\n' + ' list_signature : None\n' + ' act_gateway_time: None\n' + ) diff --git a/tests/sml/test_obis.py b/tests/sml/test_obis.py index 8a02367..b76a8d0 100644 --- a/tests/sml/test_obis.py +++ b/tests/sml/test_obis.py @@ -10,7 +10,7 @@ def test_obis(): def test_obis_invalid(): - with pytest.raises(ValueError): - build_obis(None) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 build_obis(None) + with pytest.raises(ValueError): # noqa: PT011 + build_obis('0100010800') diff --git a/tests/sml/test_time.py b/tests/sml/test_time.py index b05f91c..65a5ebc 100644 --- a/tests/sml/test_time.py +++ b/tests/sml/test_time.py @@ -1,10 +1,10 @@ from datetime import datetime import pytest +from tests.helper import in_snip from smllib.errors import UnsupportedChoiceValue from smllib.sml.sml_time import build_time -from tests.helper import in_snip def test_sml_time(): diff --git a/tests/test_frames.py b/tests/test_frames.py index 26cd2f2..0267955 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -77,15 +77,15 @@ def test_frames(frame): reader = SmlStreamReader() reader.add(a2b_hex(frame)) - frame = reader.get_frame() - assert frame is not None + parsed_frame = reader.get_frame() + assert parsed_frame is not None # ensure that parsing always works for _ in range(3): - sml_messages = frame.parse_frame() + sml_messages = parsed_frame.parse_frame() assert len(sml_messages) >= 3, sml_messages - obis_values = frame.get_obis() + obis_values = parsed_frame.get_obis() assert len(obis_values) >= 4, obis_values for obis in obis_values: diff --git a/tests/test_sml_fields.py b/tests/test_sml_fields.py index fb2ba4b..0cb0e26 100644 --- a/tests/test_sml_fields.py +++ b/tests/test_sml_fields.py @@ -1,6 +1,6 @@ from binascii import a2b_hex -from smllib.builder import create_context, SmlListEntryBuilder +from smllib.builder import SmlListEntryBuilder, create_context from smllib.sml_frame import SmlFrame diff --git a/tests/test_sml_stream_reader.py b/tests/test_sml_stream_reader.py index c934924..4d9a755 100644 --- a/tests/test_sml_stream_reader.py +++ b/tests/test_sml_stream_reader.py @@ -34,7 +34,7 @@ def test_msg_long_list(): '1b1b1b1b01010101' '76040000016200620072650000010176010107000002dba23c0b0a01484c5902000424a0010163945b00' # This frame contains a long list (f104) - '76040000026200620072650000070177010b0a01484c5902000424a00101f10477070100603201010101010104484c590177070100600100ff010101010b0a01484c5902000424a00177070100010800ff65001c81046502dba23d621e52ff6502aea1320177070100020800ff65001c81046502dba23d621e52ff62000177070100100700ff0101621b52005300890177070100200700ff0101622352ff6309280177070100340700ff0101622352ff6309290177070100480700ff0101622352ff63092201770701001f0700ff0101622152fe62290177070100330700ff0101622152fe624e0177070100470700ff0101622152fe622e0177070100510701ff01016208520062f00177070100510702ff01016208520062780177070100510704ff010162085200630110017707010051070fff010162085200630138017707010051071aff01016208520063011101770701000e0700ff0101622c52ff6301f40177070100000200000101010109312e30322e3030370177070100605a02010101010105413031410177070100600500ff0101010165001c810401010163fc1e00' # noqa: E501 + '76040000026200620072650000070177010b0a01484c5902000424a00101f10477070100603201010101010104484c590177070100600100ff010101010b0a01484c5902000424a00177070100010800ff65001c81046502dba23d621e52ff6502aea1320177070100020800ff65001c81046502dba23d621e52ff62000177070100100700ff0101621b52005300890177070100200700ff0101622352ff6309280177070100340700ff0101622352ff6309290177070100480700ff0101622352ff63092201770701001f0700ff0101622152fe62290177070100330700ff0101622152fe624e0177070100470700ff0101622152fe622e0177070100510701ff01016208520062f00177070100510702ff01016208520062780177070100510704ff010162085200630110017707010051070fff010162085200630138017707010051071aff01016208520063011101770701000e0700ff0101622c52ff6301f40177070100000200000101010109312e30322e3030370177070100605a02010101010105413031410177070100600500ff0101010165001c810401010163fc1e00' '760400000362006200726500000201710163e82300' '00001b1b1b1b1a0222ed' ) diff --git a/tox.ini b/tox.ini index ac5aa7a..8866d2c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,25 +4,21 @@ envlist = py38 py39 py310 + py311 + py312 flake [gh-actions] python = 3.8: py38 - 3.9: py39, flake + 3.9: py39 3.10: py310 + 3.11: py311 + 3.12: py312 [testenv] deps = - pytest + -r{toxinidir}/requirements_tests.txt commands = python -m pytest - -[testenv:flake] -deps = - {[testenv]deps} - flake8 - -commands = - flake8 -v