diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..b2a5220 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,27 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Test with pytest + run: | + pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c1b7250 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,62 @@ +# https://pre-commit.com/ +ci: + # The repos are quite stable and doesn't effectively change the code that + # often. So no need for the bot to update the rev that often. + autoupdate_schedule: quarterly + +# Dont format test dcm-files at all as they contain typical errors that the +# parser has to handle. +exclude: "tests/Sample.dcm" + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: debug-statements + - id: mixed-line-ending + - repo: https://github.com/MarcoGorelli/absolufy-imports + rev: v0.3.1 + hooks: + - id: absolufy-imports + name: absolufy-imports + files: ^src/dcmReader/ + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: 'v0.0.235' + hooks: + - id: ruff + args: ["--fix"] + # https://github.com/python/black#version-control-integration + - repo: https://github.com/psf/black + rev: 22.12.0 + hooks: + - id: black + - id: black-jupyter + - repo: https://github.com/keewis/blackdoc + rev: v0.3.8 + hooks: + - id: blackdoc + additional_dependencies: ["black==22.12.0"] + - id: blackdoc-autoupdate-black + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.991 + hooks: + - id: mypy + # This is slow and so we take it out of the fast-path; requires passing + # `--hook-stage manual` to pre-commit + stages: [manual] + additional_dependencies: [ + # Type stubs + types-python-dateutil, + types-pkg_resources, + types-PyYAML, + types-pytz, + typing-extensions, + ] diff --git a/README.md b/README.md index f4e43f8..1ada550 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,4 @@ This can also be used to sort a DCM file: ## UnitTests The UnitTests can be run in the tests directory by running - python Tests.py \ No newline at end of file + python Tests.py diff --git a/pyproject.toml b/pyproject.toml index 6d1c402..bfec3ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,4 +19,34 @@ disable = [ "too-many-statements", "duplicate-code", "invalid-name", -] \ No newline at end of file +] + +[tool.ruff] +target-version = "py39" +builtins = ["ellipsis"] +exclude = [ + ".eggs", + "doc", +] +# E402: module level import not at top of file +# E501: line too long - let black worry about that +# E731: do not assign a lambda expression, use a def +ignore = [ + "E402", + "E501", + "E731", +] +select = [ + # Pyflakes + "F", + # Pycodestyle + "E", + "W", + # isort + "I", + # Pyupgrade + "UP", +] + +[tool.ruff.isort] +known-first-party = ["dcmReader"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6abff1b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# This file is redundant with setup.cfg; +# it exists to let GitHub build the repository dependency graph +# https://help.github.com/en/github/visualizing-repository-data-with-graphs/listing-the-packages-that-a-repository-depends-on + +numpy >= 1.21 diff --git a/setup.cfg b/setup.cfg index ae22d85..ae02f85 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,30 @@ classifiers = package_dir = = src packages = find: -python_requires = >=3.6 +python_requires = >=3.9 +install_requires = + numpy >= 1.21 [options.packages.find] -where = src \ No newline at end of file +where = src + +[flake8] +ignore = + # E203: whitespace before ':' - doesn't work well with black + # E402: module level import not at top of file + # E501: line too long - let black worry about that + # E731: do not assign a lambda expression, use a def + # W503: line break before binary operator + E203, E402, E501, E731, W503 +exclude = + .eggs + doc +builtins = + ellipsis + +[isort] +profile = black +skip_gitignore = true +float_to_top = true +default_section = THIRDPARTY +known_first_party = dcmReader diff --git a/setup.py b/setup.py index 95dca7e..b0798ae 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import setuptools -with open("README.md", "r", encoding="utf-8") as fh: +with open("README.md", encoding="utf-8") as fh: long_description = fh.read() setuptools.setup( diff --git a/src/dcmReader/dcm_characteristic_line.py b/src/dcmReader/dcm_characteristic_line.py index 0e7948f..f26b1f4 100644 --- a/src/dcmReader/dcm_characteristic_line.py +++ b/src/dcmReader/dcm_characteristic_line.py @@ -54,15 +54,15 @@ def __str__(self): if self.unit_values: value += f' EINHEIT_W "{self.unit_values}"\n' if self.x_mapping: - value += f'*SSTX {self.x_mapping}\n' + value += f"*SSTX {self.x_mapping}\n" if self.values: x_entries = "" value_entries = "" for x_entry, value_entry in self.values.items(): x_entries += f"{str(x_entry)} " value_entries += f"{str(value_entry)} " - value += f' ST/X {x_entries.strip()}\n' - value += f' WERT {value_entries.strip()}\n' + value += f" ST/X {x_entries.strip()}\n" + value += f" WERT {value_entries.strip()}\n" for var_name, var_value in self.variants.items(): value += f" VAR {var_name}={var_value}\n" diff --git a/src/dcmReader/dcm_characteristic_map.py b/src/dcmReader/dcm_characteristic_map.py index 648e17c..bc95c73 100644 --- a/src/dcmReader/dcm_characteristic_map.py +++ b/src/dcmReader/dcm_characteristic_map.py @@ -64,9 +64,9 @@ def __str__(self): if self.unit_values: value += f' EINHEIT_W "{self.unit_values}"\n' if self.x_mapping: - value += f'*SSTX {self.x_mapping}\n' + value += f"*SSTX {self.x_mapping}\n" if self.y_mapping: - value += f'*SSTY {self.y_mapping}\n' + value += f"*SSTY {self.y_mapping}\n" stx_written = False for y_entry, map_values in self.values.items(): x_entries = "" @@ -75,10 +75,10 @@ def __str__(self): x_entries += f"{str(x_entry)} " value_entries += f"{str(value_entry)} " if not stx_written: - value += f' ST/X {x_entries.strip()}\n' + value += f" ST/X {x_entries.strip()}\n" stx_written = True - value += f' ST/Y {str(y_entry).strip()}\n' - value += f' WERT {value_entries.strip()}\n' + value += f" ST/Y {str(y_entry).strip()}\n" + value += f" WERT {value_entries.strip()}\n" for var_name, var_value in self.variants.items(): value += f" VAR {var_name}={var_value}\n" diff --git a/src/dcmReader/dcm_distribution.py b/src/dcmReader/dcm_distribution.py index 6da8fde..91387f3 100644 --- a/src/dcmReader/dcm_distribution.py +++ b/src/dcmReader/dcm_distribution.py @@ -49,7 +49,7 @@ def __str__(self): x_entries = "" for x_entry in self.values: x_entries += f"{str(x_entry)} " - value += f' ST/X {x_entries.strip()}\n' + value += f" ST/X {x_entries.strip()}\n" for var_name, var_value in self.variants.items(): value += f" VAR {var_name}={var_value}\n" diff --git a/src/dcmReader/dcm_fixed_characteristic_line.py b/src/dcmReader/dcm_fixed_characteristic_line.py index 64ddfa4..e474da2 100644 --- a/src/dcmReader/dcm_fixed_characteristic_line.py +++ b/src/dcmReader/dcm_fixed_characteristic_line.py @@ -3,6 +3,7 @@ """ from dataclasses import dataclass + from dcmReader.dcm_characteristic_line import DcmCharacteristicLine diff --git a/src/dcmReader/dcm_fixed_characteristic_map.py b/src/dcmReader/dcm_fixed_characteristic_map.py index 88423de..021c1c8 100644 --- a/src/dcmReader/dcm_fixed_characteristic_map.py +++ b/src/dcmReader/dcm_fixed_characteristic_map.py @@ -3,6 +3,7 @@ """ from dataclasses import dataclass + from dcmReader.dcm_characteristic_map import DcmCharacteristicMap diff --git a/src/dcmReader/dcm_group_characteristic_line.py b/src/dcmReader/dcm_group_characteristic_line.py index a518187..eb6573e 100644 --- a/src/dcmReader/dcm_group_characteristic_line.py +++ b/src/dcmReader/dcm_group_characteristic_line.py @@ -2,6 +2,7 @@ Definition of DCM group characteristic line """ from dataclasses import dataclass + from dcmReader.dcm_characteristic_line import DcmCharacteristicLine diff --git a/src/dcmReader/dcm_group_characteristic_map.py b/src/dcmReader/dcm_group_characteristic_map.py index 4813dd1..4ad8190 100644 --- a/src/dcmReader/dcm_group_characteristic_map.py +++ b/src/dcmReader/dcm_group_characteristic_map.py @@ -2,6 +2,7 @@ Definition of DCM fixed characteristic map """ from dataclasses import dataclass + from dcmReader.dcm_characteristic_map import DcmCharacteristicMap diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 8dfeac4..8be3526 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -2,20 +2,20 @@ DCMReader which handles data parsing. """ +import logging import os import re -import logging -from dcmReader.dcm_parameter import DcmParameter -from dcmReader.dcm_function import DcmFunction -from dcmReader.dcm_parameter_block import DcmParameterBlock from dcmReader.dcm_characteristic_line import DcmCharacteristicLine -from dcmReader.dcm_fixed_characteristic_line import DcmFixedCharacteristicLine -from dcmReader.dcm_group_characteristic_line import DcmGroupCharacteristicLine from dcmReader.dcm_characteristic_map import DcmCharacteristicMap +from dcmReader.dcm_distribution import DcmDistribution +from dcmReader.dcm_fixed_characteristic_line import DcmFixedCharacteristicLine from dcmReader.dcm_fixed_characteristic_map import DcmFixedCharacteristicMap +from dcmReader.dcm_function import DcmFunction +from dcmReader.dcm_group_characteristic_line import DcmGroupCharacteristicLine from dcmReader.dcm_group_characteristic_map import DcmGroupCharacteristicMap -from dcmReader.dcm_distribution import DcmDistribution +from dcmReader.dcm_parameter import DcmParameter +from dcmReader.dcm_parameter_block import DcmParameterBlock logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -125,7 +125,7 @@ def read(self, file) -> None: comment_qualifier = ("!", "*", ".") - with open(file, "r", encoding="utf-8") as dcm_file: + with open(file, encoding="utf-8") as dcm_file: for line in dcm_file: # Remove whitespaces line = line.strip() @@ -253,12 +253,16 @@ def read(self, file) -> None: line = dcm_file.readline().strip() if line.startswith("END"): if len(stx) != found_characteristic_line.x_dimension: - logger.error("X dimension in %s \ - do not match description!", found_characteristic_line.name) + logger.error( + "X dimension in %s \ + do not match description!", + found_characteristic_line.name, + ) if len(parameters) != found_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_characteristic_line.name + do not match description!", + found_characteristic_line.name, ) found_characteristic_line.values = dict(zip(stx, parameters)) break @@ -311,7 +315,8 @@ def read(self, file) -> None: if len(parameters) != found_fixed_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_fixed_characteristic_line.name + do not match description!", + found_fixed_characteristic_line.name, ) found_fixed_characteristic_line.values = dict(zip(stx, parameters)) break @@ -360,12 +365,14 @@ def read(self, file) -> None: if len(parameters) != found_group_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_group_characteristic_line.name + do not match description!", + found_group_characteristic_line.name, ) if len(stx) != found_group_characteristic_line.x_dimension: logger.error( "X dimension in %s \ - do not match description!", found_group_characteristic_line.name + do not match description!", + found_group_characteristic_line.name, ) found_group_characteristic_line.values = dict(zip(stx, parameters)) break @@ -415,16 +422,21 @@ def read(self, file) -> None: if len(found_characteristic_map.values) != found_characteristic_map.y_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_characteristic_map.name + does not match description!", + found_characteristic_map.name, ) if len(stx) != found_characteristic_map.x_dimension: - logger.error("X dimension in %s \ - do not match description!", found_characteristic_map.name) + logger.error( + "X dimension in %s \ + do not match description!", + found_characteristic_map.name, + ) for name, entry in found_characteristic_map.values.items(): if len(entry) != found_characteristic_map.x_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_characteristic_map.name + does not match description!", + found_characteristic_map.name, ) else: found_characteristic_map.values[name] = dict(zip(stx, entry)) @@ -487,7 +499,8 @@ def read(self, file) -> None: if len(found_fixed_characteristic_map.values) != found_fixed_characteristic_map.y_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_fixed_characteristic_map.name + does not match description!", + found_fixed_characteristic_map.name, ) if len(stx) != found_fixed_characteristic_map.x_dimension: logger.error( @@ -497,7 +510,8 @@ def read(self, file) -> None: if len(entry) != found_fixed_characteristic_map.x_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_fixed_characteristic_map.name + does not match description!", + found_fixed_characteristic_map.name, ) else: found_fixed_characteristic_map.values[name] = dict(zip(stx, entry)) @@ -560,7 +574,8 @@ def read(self, file) -> None: if len(found_group_characteristic_map.values) != found_group_characteristic_map.y_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_group_characteristic_map.name + does not match description!", + found_group_characteristic_map.name, ) if len(stx) != found_group_characteristic_map.x_dimension: logger.error( @@ -570,7 +585,8 @@ def read(self, file) -> None: if len(entry) != found_group_characteristic_map.x_dimension: logger.error( "Values dimension in %s \ - does not match description!", found_group_characteristic_map.name + does not match description!", + found_group_characteristic_map.name, ) else: found_group_characteristic_map.values[name] = dict(zip(stx, entry)) diff --git a/tests/Tests.py b/tests/test_dcmreader.py similarity index 96% rename from tests/Tests.py rename to tests/test_dcmreader.py index 79fbc0e..5b1b9fc 100644 --- a/tests/Tests.py +++ b/tests/test_dcmreader.py @@ -2,12 +2,12 @@ import sys import unittest +from dcmReader.dcm_reader import DcmReader + testdir = os.path.dirname(__file__) srcdir = "../src" sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir))) -from dcmReader.dcm_reader import DcmReader - class TestWriteFile(unittest.TestCase): def test_fileWriting(self): @@ -196,9 +196,7 @@ def test_fixedCharacteristicLine(self): self.assertEqual("fixedCharacteristicLine", characteristic.name) self.assertEqual("Sample fixed characteristic line", characteristic.description) self.assertEqual("FixedCharacteristicLineFunction", characteristic.function) - self.assertEqual( - "FixedCharacteristicLineDisplayname", characteristic.display_name - ) + self.assertEqual("FixedCharacteristicLineDisplayname", characteristic.display_name) self.assertEqual("°", characteristic.unit_values) self.assertEqual("s", characteristic.unit_x) self.assertEqual(45.0, characteristic.values[0.0]) @@ -215,9 +213,7 @@ def test_fixedCharacteristicLine(self): self.assertEqual("fixedCharacteristicLine", characteristicWritten.name) self.assertEqual("Sample fixed characteristic line", characteristicWritten.description) self.assertEqual("FixedCharacteristicLineFunction", characteristicWritten.function) - self.assertEqual( - "FixedCharacteristicLineDisplayname", characteristicWritten.display_name - ) + self.assertEqual("FixedCharacteristicLineDisplayname", characteristicWritten.display_name) self.assertEqual("°", characteristicWritten.unit_values) self.assertEqual("s", characteristicWritten.unit_x) self.assertEqual(45.0, characteristicWritten.values[0.0]) @@ -242,9 +238,7 @@ def test_groupCharacteristicLine(self): self.assertEqual("groupCharacteristicLine", characteristic.name) self.assertEqual("Sample group characteristic line", characteristic.description) self.assertEqual("GroupCharacteristicLineFunction", characteristic.function) - self.assertEqual( - "GroupCharacteristicLineDisplayname", characteristic.display_name - ) + self.assertEqual("GroupCharacteristicLineDisplayname", characteristic.display_name) self.assertEqual("°", characteristic.unit_values) self.assertEqual("s", characteristic.unit_x) self.assertEqual(-45.0, characteristic.values[1.0]) @@ -258,9 +252,7 @@ def test_groupCharacteristicLine(self): self.assertEqual("groupCharacteristicLine", characteristicWritten.name) self.assertEqual("Sample group characteristic line", characteristicWritten.description) self.assertEqual("GroupCharacteristicLineFunction", characteristicWritten.function) - self.assertEqual( - "GroupCharacteristicLineDisplayname", characteristicWritten.display_name - ) + self.assertEqual("GroupCharacteristicLineDisplayname", characteristicWritten.display_name) self.assertEqual("°", characteristicWritten.unit_values) self.assertEqual("s", characteristicWritten.unit_x) self.assertEqual(-45.0, characteristicWritten.values[1.0]) @@ -342,9 +334,7 @@ def test_fixedCharacteristicMap(self): self.assertEqual("fixedCharacteristicMap", characteristic.name) self.assertEqual("Sample fixed characteristic map", characteristic.description) self.assertEqual("FixedCharacteristicMapFunction", characteristic.function) - self.assertEqual( - "FixedCharacteristicMapDisplayname", characteristic.display_name - ) + self.assertEqual("FixedCharacteristicMapDisplayname", characteristic.display_name) self.assertEqual("bar", characteristic.unit_values) self.assertEqual("°C", characteristic.unit_x) self.assertEqual("m/s", characteristic.unit_y) @@ -369,9 +359,7 @@ def test_fixedCharacteristicMap(self): self.assertEqual("fixedCharacteristicMap", characteristicWritten.name) self.assertEqual("Sample fixed characteristic map", characteristicWritten.description) self.assertEqual("FixedCharacteristicMapFunction", characteristicWritten.function) - self.assertEqual( - "FixedCharacteristicMapDisplayname", characteristicWritten.display_name - ) + self.assertEqual("FixedCharacteristicMapDisplayname", characteristicWritten.display_name) self.assertEqual("bar", characteristicWritten.unit_values) self.assertEqual("°C", characteristicWritten.unit_x) self.assertEqual("m/s", characteristicWritten.unit_y) @@ -404,9 +392,7 @@ def test_groupCharacteristicMap(self): self.assertEqual("groupCharacteristicMap", characteristic.name) self.assertEqual("Sample group characteristic map", characteristic.description) self.assertEqual("GroupCharacteristicMapFunction", characteristic.function) - self.assertEqual( - "GroupCharacteristicMapDisplayname", characteristic.display_name - ) + self.assertEqual("GroupCharacteristicMapDisplayname", characteristic.display_name) self.assertEqual("bar", characteristic.unit_values) self.assertEqual("°C", characteristic.unit_x) self.assertEqual("m/s", characteristic.unit_y) @@ -437,9 +423,7 @@ def test_groupCharacteristicMap(self): self.assertEqual("groupCharacteristicMap", characteristicWritten.name) self.assertEqual("Sample group characteristic map", characteristicWritten.description) self.assertEqual("GroupCharacteristicMapFunction", characteristicWritten.function) - self.assertEqual( - "GroupCharacteristicMapDisplayname", characteristicWritten.display_name - ) + self.assertEqual("GroupCharacteristicMapDisplayname", characteristicWritten.display_name) self.assertEqual("bar", characteristicWritten.unit_values) self.assertEqual("°C", characteristicWritten.unit_x) self.assertEqual("m/s", characteristicWritten.unit_y)