diff --git a/.env b/.env deleted file mode 100644 index d28a0ee..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -PYTHONPATH=src \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..0b2f9e3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +name: Publish + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + pypi-publish: + name: Upload release to PyPi + runs-on: ubuntu-latest + environment: + name: pypi + url: https://test.pypi.org/p/objdictgen + permissions: + id-token: write + strategy: + matrix: + python-version: ["3.13"] + + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + + - name: Build package + run: uv build + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + + # - name: Publish package to TestPyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # password: ${{ secrets.TEST_PYPI_API_TOKEN }} + # repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index 0634179..0000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Python package - -on: - push: - branches: - - '**' - pull_request: - types: [opened, synchronize, reopened] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - python-version: ["3.11"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: '**/setup.cfg' - # This is needed for compiling wxwidgets - - name: Install gtk3 - run: | - sudo apt update - sudo apt install libgtk-3-dev - - name: Install dependencies - run: | - python -m venv venv - venv/bin/python -m pip install --upgrade pip setuptools - venv/bin/python -m pip install -e .[dev] - - name: Test with pytest - run: | - venv/bin/pytest --cov=objdictgen --cov-report=xml --cov-branch -p no:logging --tb=no - # - name: Upload coverage reports to Codecov - # uses: codecov/codecov-action@v4.0.1 - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - # slug: Laerdal/python-objdictgen - - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@v5.1.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 6fd0741..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,50 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package - -on: - release: - types: [published] - -permissions: - contents: read - -jobs: - deploy: - - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - python-version: ["3.x"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: '**/setup.cfg' - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - python -m pip install build - - name: Build package - run: python -m build - # - name: Publish package to TestPyPI - # uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - # with: - # password: ${{ secrets.TEST_PYPI_API_TOKEN }} - # repository_url: https://test.pypi.org/legacy/ - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..b75829a --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,78 @@ +name: Validate + +on: + push: + branches: + - '**' + pull_request: + types: [opened, synchronize, reopened] + +jobs: + + pytest: + name: Unit testing + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.13"] + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 # To ensure non-shallow git clones for sonar + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: Run pytest + run: | + pytest --cov=objdictgen --cov-report=xml --cov-branch -p no:logging --tb=no + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@v6.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + + mypy: + name: Static type analysis + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: Run mypy + run: | + mypy src/objdictgen + continue-on-error: true + + + ruff: + name: Code linting + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + - name: Run ruff check + uses: astral-sh/ruff-action@v3 + with: + args: check --exit-zero + src: "." diff --git a/.gitignore b/.gitignore index a90c04e..3341342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/venv*/ +venv*/ +.venv*/ *.code-workspace .vscode/ *.egg-info/ @@ -10,6 +11,11 @@ tmp*/ dist/ .nox coverage.xml +.ruff_cache/ +.mypy_cache/ +uv.lock +bug_report*.txt +bin/ *.pyc *.bak @@ -19,10 +25,6 @@ __* _tmp* tests/od/extra-compare* -/*.od -/*.json -/*.jsonc - -objdictgen.* -fw-can-shared -bug_report*.txt +*.od +*.json +*.jsonc diff --git a/README.md b/README.md index 57b418e..9895838 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,27 @@ and `.eds` files. It can generate c code for use with the canfestival library. ## Install -To install into a virtual environment `venv`. Check out this repo and go to -the top in a command-prompt (here assuming Windows and git bash): +To use this package Python3 must be installed. Use the package manager of +choice to install the package in a virtual manager. We recommend +the [uv package manager](https://docs.astral.sh/uv/). + +Using uv (one of many methods) + + $ uv venv + $ uv pip install objdictgen[ui] # [ui] will install GUI tools + $ uv run odg + +Using pip (for Windows) $ py -3 -mvenv venv - $ venv/Scripts/python -mpip install --upgrade pip setuptools # Optional - $ venv/Scripts/pip install objdictgen[ui] # [ui] will install GUI tools + $ venv/Scripts/pip install objdictgen[ui] + +After this `venv/Scripts/odg.exe` (on Windows) or `venv\bin\odg` executable +exists and can be called directly to run the command. -After this `venv/Scripts/odg.exe` (on Windows) will exist and can be called -from anywhere to run it. +The `objdictgen[ui]` suffix will install the wx dependency needed for the UI +`odg edit`. If no UI is needed, this suffix can be omitted. -The `[ui]` suffix to `pip install` will install the wx dependency needed -for the UI `odg edit`. If no UI is needed, this suffix can be omitted. ## `odg` command-line tool @@ -133,4 +142,4 @@ original work from CanFestival is: The Python 3 port and tool improvements have been implemented under - Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS + Copyright (C) 2022-2025 Svein Seldal, Laerdal Medical AS diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 6170fb9..0000000 --- a/noxfile.py +++ /dev/null @@ -1,8 +0,0 @@ -import nox - -@nox.session( - python=["3.12", "3.11", "3.10"] -) -def test(session): - session.install(".[dev]") - session.run("pytest") diff --git a/packaging/filereplacer.py b/packaging/filereplacer.py index 33d88ba..707c800 100644 --- a/packaging/filereplacer.py +++ b/packaging/filereplacer.py @@ -1,12 +1,11 @@ +import importlib.metadata import os import re -import warnings -import objdictgen -from setuptools import SetuptoolsDeprecationWarning -from setuptools.config import read_configuration +from pprint import pprint +from typing import Any -warnings.filterwarnings("ignore", category=SetuptoolsDeprecationWarning) +import objdictgen def convert(infile, outfile): @@ -14,11 +13,20 @@ def convert(infile, outfile): pat = re.compile(r'^(.*?)@@{(.[^}]+)}(.*)$', re.S) - config = read_configuration('setup.cfg')["metadata"] + config: dict[str, Any] = dict(importlib.metadata.metadata("objdictgen")) + + # Generate a 4-tuple version number + version = objdictgen.__version__ + version_tuple = tuple(int(x) for x in version.split('.') if x.isdigit()) + version_tuple = version_tuple + (0,) * (4 - len(version_tuple)) - # Some hacks - config['version_tuple'] = objdictgen.__version_tuple__ - config['copyright'] = objdictgen.__copyright__ + config['_version_tuple'] = version_tuple + config['_copyright'] = objdictgen.__copyright__ + + # Shorten description for file description field and print it + pr_config = config.copy() + pr_config["Description"] = pr_config["Description"][0:60] + "..." + pprint(pr_config) with open(infile, "r", encoding="utf-8") as fin: out = '' @@ -45,6 +53,8 @@ def convert(infile, outfile): with open(outfile, 'w', encoding="utf-8") as fout: fout.write(out) + print(f"Converted {infile} -> {outfile}\n{out}") + if __name__ == '__main__': import argparse diff --git a/packaging/objdictedit.ver.in b/packaging/objdictedit.ver.in index c90b9f1..00f5097 100644 --- a/packaging/objdictedit.ver.in +++ b/packaging/objdictedit.ver.in @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. -filevers=@@{version_tuple}, -prodvers=@@{version_tuple}, +filevers=@@{_version_tuple}, +prodvers=@@{_version_tuple}, # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -30,13 +30,13 @@ StringFileInfo( StringTable( u'040904B0', [StringStruct(u'CompanyName', u'Object Dictionary Editor'), - StringStruct(u'FileDescription', u'@@{description}'), - StringStruct(u'FileVersion', u'@@{version}'), + StringStruct(u'FileDescription', u'@@{Summary}'), + StringStruct(u'FileVersion', u'@@{Version}'), StringStruct(u'InternalName', u'objdictedit'), - StringStruct(u'LegalCopyright', u'@@{copyright}'), + StringStruct(u'LegalCopyright', u'@@{_copyright}'), StringStruct(u'OriginalFilename', u'objdictedit.exe'), StringStruct(u'ProductName', u'objdictedit'), - StringStruct(u'ProductVersion', u'@@{version}')]) + StringStruct(u'ProductVersion', u'@@{Version}')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] diff --git a/pyproject.toml b/pyproject.toml index 8f12171..4131ed9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,65 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" +requires = ["uv_build>=0.9.5,<0.10.0"] +build-backend = "uv_build" + +[project] +name = "objdictgen" +version = "3.5.6" +authors = [ + {name = "Svein Seldal", email = "sveinse@laerdal.com"}, +] +description = "CanFestival Object Dictionary tool" +readme = "README.md" +license = "LGPL-2.1-or-later" +requires-python = ">=3.10,<4" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: Embedded Systems", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", +] +dependencies = [ + "jsonschema", + "colorama", + "deepdiff", +] + +[project.optional-dependencies] +ui = [ + "wxPython", +] +dev = [ + "ruff", + "mypy", + "types-setuptools", + "types-colorama", + "types-wxpython", + "pytest", + "coverage", + "pytest-cov", + "pytest-mock", + "freezegun", +] + +[project.urls] +Repository = "https://github.com/Laerdal/python-objdictgen" +"Bug Tracker" = "https://github.com/Laerdal/python-objdictgen/issues" -#=================== -# PYTEST -#=================== +[project.scripts] +odg = "objdictgen.__main__:main" + +[tool.uv.build-backend] +source-include = [ + "tests/**", +] [tool.pytest.ini_options] minversion = "6.0" @@ -14,593 +69,5 @@ testpaths = [ ] filterwarnings = "ignore::DeprecationWarning" -#=================== -# MYPY -#=================== - -[tool.mypy] -# enable_incomplete_feature = ["Unpack"] - -# Start off with these -warn_unused_configs = true -warn_redundant_casts = true -warn_unused_ignores = true - -# Getting these passing should be easy -strict_equality = true -strict_concatenate = true - -# Strongly recommend enabling this one as soon as you can -check_untyped_defs = true - -# These shouldn't be too much additional work, but may be tricky to -# get passing if you use a lot of untyped libraries -# disallow_subclassing_any = true -# disallow_untyped_decorators = true -# disallow_any_generics = true - -# These next few are various gradations of forcing use of type annotations -# disallow_untyped_calls = true -# disallow_incomplete_defs = true -# disallow_untyped_defs = true - -# This one isn't too hard to get passing, but return on investment is lower -# no_implicit_reexport = true - -# This one can be tricky to get passing if you use a lot of untyped libraries -# warn_return_any = true - -#=================== -# PYLINT -#=================== - -[tool.pylint.main] -# Analyse import fallback blocks. This can be used to support both Python 2 and 3 -# compatible code, which means that the block might have code that exists only in -# one or another interpreter, leading to false positives when analysed. -# analyse-fallback-blocks = - -# Clear in-memory caches upon conclusion of linting. Useful if running pylint in -# a server-like mode. -# clear-cache-post-run = - -# Always return a 0 (non-error) status code, even if lint errors are found. This -# is primarily useful in continuous integration scripts. -# exit-zero = - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -# extension-pkg-allow-list = - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -# objdictgen: add this -extension-pkg-whitelist = "wx" - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -# fail-on = - -# Specify a score threshold under which the program will exit with error. -fail-under = 10 - -# Interpret the stdin as a python script, whose filename needs to be passed as -# the module_or_package argument. -# from-stdin = - -# Files or directories to be skipped. They should be base names, not paths. -ignore = ["CVS"] - -# Add files or directories matching the regular expressions patterns to the -# ignore-list. The regex matches against paths and can be in Posix or Windows -# format. Because '\\' represents the directory delimiter on Windows systems, it -# can't be used as an escape character. -# ignore-paths = - -# Files or directories matching the regular expression patterns are skipped. The -# regex matches against base names, not paths. The default value ignores Emacs -# file locks -ignore-patterns = ["^\\.#"] - -# List of module names for which member attributes should not be checked (useful -# for modules/projects where namespaces are manipulated during runtime and thus -# existing member attributes cannot be deduced by static analysis). It supports -# qualified module names, as well as Unix pattern matching. -# ignored-modules = - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -# init-hook = - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use, and will cap the count on Windows to -# avoid hangs. -jobs = 1 - -# Control the amount of potential inferred values when inferring a single object. -# This can help the performance when dealing with large functions or complex, -# nested conditions. -limit-inference-results = 100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -# load-plugins = -load-plugins=[ "pylint.extensions.no_self_use" ] - -# Pickle collected data for later comparisons. -persistent = true - -# Minimum Python version to use for version dependent checks. Will default to the -# version used to run pylint. -py-version = "3.11" - -# Discover python modules and packages in the file system subtree. -# recursive = - -# Add paths to the list of the source roots. Supports globbing patterns. The -# source root is an absolute path or a path relative to the current working -# directory used to determine a package namespace for modules located under the -# source root. -# source-roots = - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode = true - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -# unsafe-load-any-extension = - -[tool.pylint.basic] -# Naming style matching correct argument names. -argument-naming-style = "snake_case" - -# Regular expression matching correct argument names. Overrides argument-naming- -# style. If left empty, argument names will be checked with the set naming style. -# argument-rgx = - -# Naming style matching correct attribute names. -# objdictgen: was "snake_case" -attr-naming-style = "any" - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. If left empty, attribute names will be checked with the set naming -# style. -# attr-rgx = - -# Bad variable names which should always be refused, separated by a comma. -bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"] - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -# bad-names-rgxs = - -# Naming style matching correct class attribute names. -class-attribute-naming-style = "any" - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. If left empty, class attribute names will be checked -# with the set naming style. -# class-attribute-rgx = - -# Naming style matching correct class constant names. -class-const-naming-style = "UPPER_CASE" - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. If left empty, class constant names will be checked with -# the set naming style. -# class-const-rgx = - -# Naming style matching correct class names. -class-naming-style = "PascalCase" - -# Regular expression matching correct class names. Overrides class-naming-style. -# If left empty, class names will be checked with the set naming style. -# class-rgx = - -# Naming style matching correct constant names. -const-naming-style = "UPPER_CASE" - -# Regular expression matching correct constant names. Overrides const-naming- -# style. If left empty, constant names will be checked with the set naming style. -# const-rgx = - -# Minimum line length for functions/classes that require docstrings, shorter ones -# are exempt. -docstring-min-length = -1 - -# Naming style matching correct function names. -function-naming-style = "snake_case" - -# Regular expression matching correct function names. Overrides function-naming- -# style. If left empty, function names will be checked with the set naming style. -# function-rgx = - -# Good variable names which should always be accepted, separated by a comma. -good-names = ["i", "j", "k", "ex", "Run", "_"] - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -# good-names-rgxs = - -# Include a hint for the correct naming format with invalid-name. -# include-naming-hint = - -# Naming style matching correct inline iteration names. -inlinevar-naming-style = "any" - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. If left empty, inline iteration names will be checked -# with the set naming style. -# inlinevar-rgx = - -# Naming style matching correct method names. -# Objdictgen: was "snake_case" -method-naming-style = "any" - -# Regular expression matching correct method names. Overrides method-naming- -# style. If left empty, method names will be checked with the set naming style. -# method-rgx = - -# Naming style matching correct module names. -module-naming-style = "snake_case" - -# Regular expression matching correct module names. Overrides module-naming- -# style. If left empty, module names will be checked with the set naming style. -# module-rgx = - -# Colon-delimited sets of names that determine each other's naming style when the -# name regexes allow several styles. -# name-group = - -# Regular expression which should only match function or class names that do not -# require a docstring. -no-docstring-rgx = "^_" - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. These -# decorators are taken in consideration only for invalid-name. -property-classes = ["abc.abstractproperty"] - -# Regular expression matching correct type alias names. If left empty, type alias -# names will be checked with the set naming style. -# typealias-rgx = - -# Regular expression matching correct type variable names. If left empty, type -# variable names will be checked with the set naming style. -# typevar-rgx = - -# Naming style matching correct variable names. -variable-naming-style = "snake_case" - -# Regular expression matching correct variable names. Overrides variable-naming- -# style. If left empty, variable names will be checked with the set naming style. -# variable-rgx = - -[tool.pylint.classes] -# Warn about protected attribute access inside special methods -# check-protected-access-in-special-methods = - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods = ["__init__", "__new__", "setUp", "asyncSetUp", "__post_init__"] - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make", "os._exit"] - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg = ["cls"] - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg = ["mcs"] - -[tool.pylint.design] -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -# exclude-too-few-public-methods = - -# List of qualified class names to ignore when counting class parents (see R0901) -# ignored-parents = - -# Maximum number of arguments for function / method. -max-args = 5 - -# Maximum number of attributes for a class (see R0902). -max-attributes = 7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr = 5 - -# Maximum number of branch for function / method body. -max-branches = 12 - -# Maximum number of locals for function / method body. -max-locals = 15 - -# Maximum number of parents for a class (see R0901). -max-parents = 7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods = 20 - -# Maximum number of return / yield for function / method body. -max-returns = 6 - -# Maximum number of statements in function / method body. -max-statements = 50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods = 2 - -[tool.pylint.exceptions] -# Exceptions that will emit a warning when caught. -overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"] - -[tool.pylint.format] -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format = - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines = "^\\s*(# )??$" - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren = 4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string = " " - -# Maximum number of characters on a single line. -# objdictgen: was 100 -# FIXME: Set to shorter -max-line-length = 120 - -# Maximum number of lines in a module. -# objdictgen: orig was 1000 -max-module-lines = 1500 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -# single-line-class-stmt = - -# Allow the body of an if to be on the same line as the test if there is no else. -# single-line-if-stmt = - -[tool.pylint.imports] -# List of modules that can be imported at any level, not just the top level one. -# allow-any-import-level = - -# Allow explicit reexports by alias from a package __init__. -# allow-reexport-from-package = - -# Allow wildcard imports from modules that define __all__. -# allow-wildcard-with-all = - -# Deprecated modules which should not be used, separated by a comma. -# deprecated-modules = - -# Output a graph (.gv or any supported image format) of external dependencies to -# the given file (report RP0402 must not be disabled). -# ext-import-graph = - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be disabled). -# import-graph = - -# Output a graph (.gv or any supported image format) of internal dependencies to -# the given file (report RP0402 must not be disabled). -# int-import-graph = - -# Force import order to recognize a module as part of the standard compatibility -# libraries. -# known-standard-library = - -# Force import order to recognize a module as part of a third party library. -known-third-party = ["enchant"] - -# Couples of modules and preferred modules, separated by a comma. -# preferred-modules = - -[tool.pylint.logging] -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style = "old" - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules = ["logging"] - -[tool.pylint."messages control"] -# Only show warnings with the listed confidence levels. Leave empty to show all. -# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] - -# Disable the message, report, category or checker with the given id(s). You can -# either give multiple identifiers separated by comma (,) or put this option -# multiple times (only on the command line, not in the configuration file where -# it should appear only once). You can also use "--disable=all" to disable -# everything first and then re-enable specific checks. For example, if you want -# to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -# objdictgen: orig: "raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero", -disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero", -"missing-function-docstring", "duplicate-code" -] - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where it -# should appear only once). See also the "--disable" option for examples. -# objdictgen: orig: # enable = -enable = ["no-self-use"] - -[tool.pylint.method_args] -# List of qualified names (i.e., library.method) which require a timeout -# parameter e.g. 'requests.api.get,requests.api.post' -timeout-methods = ["requests.api.delete", "requests.api.get", "requests.api.head", "requests.api.options", "requests.api.patch", "requests.api.post", "requests.api.put", "requests.api.request"] - -[tool.pylint.miscellaneous] -# List of note tags to take in consideration, separated by a comma. -notes = ["FIXME", "XXX", "TODO"] - -# Regular expression of note tags to take in consideration. -# notes-rgx = - -[tool.pylint.refactoring] -# Maximum number of nested blocks for function / method body -max-nested-blocks = 5 - -# Complete name of functions that never returns. When checking for inconsistent- -# return-statements if a never returning function is called then it will be -# considered as an explicit return statement and no message will be printed. -never-returning-functions = ["sys.exit", "argparse.parse_error"] - -[tool.pylint.reports] -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'fatal', 'error', 'warning', 'refactor', -# 'convention', and 'info' which contain the number of messages in each category, -# as well as 'statement' which is the total number of statements analyzed. This -# score is used by the global evaluation report (RP0004). -evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))" - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -# msg-template = - -# Set the output format. Available formats are: text, parseable, colorized, json2 -# (improved json format), json (old json format) and msvs (visual studio). You -# can also give a reporter class, e.g. mypackage.mymodule.MyReporterClass. -output-format = "colorized" - -# Tells whether to display a full report or only the messages. -# reports = - -# Activate the evaluation score. -score = true - -[tool.pylint.similarities] -# Comments are removed from the similarity computation -ignore-comments = true - -# Docstrings are removed from the similarity computation -ignore-docstrings = true - -# Imports are removed from the similarity computation -ignore-imports = true - -# Signatures are removed from the similarity computation -ignore-signatures = true - -# Minimum lines number of a similarity. -min-similarity-lines = 4 - -[tool.pylint.spelling] -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions = 4 - -# Spelling dictionary name. No available dictionaries : You need to install both -# the python package and the system dependency for enchant to work. -# spelling-dict = - -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" - -# List of comma separated words that should not be checked. -# spelling-ignore-words = - -# A path to a file that contains the private dictionary; one word per line. -# spelling-private-dict-file = - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -# spelling-store-unknown-words = - -[tool.pylint.typecheck] -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators = ["contextlib.contextmanager"] - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -# objdictgen: added -generated-members = "wx.*" - -# Tells whether missing members accessed in mixin class should be ignored. A -# class is considered mixin if its name matches the mixin-class-rgx option. -# Tells whether to warn about missing members when the owner of the attribute is -# inferred to be None. -ignore-none = true - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference can -# return multiple potential results while evaluating a Python object, but some -# branches might not be evaluated, which results in partial inference. In that -# case, it might be useful to still emit no-member and other checks for the rest -# of the inferred objects. -ignore-on-opaque-inference = true - -# List of symbolic message names to ignore for Mixin members. -ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes = ["optparse.Values", "thread._local", "_thread._local", "argparse.Namespace"] - -# Show a hint with possible names when a member name was not found. The aspect of -# finding the hint is based on edit distance. -missing-member-hint = true - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance = 1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices = 1 - -# Regex pattern to define which classes are considered mixins. -mixin-class-rgx = ".*[Mm]ixin" - -# List of decorators that change the signature of a decorated function. -# signature-mutators = - -[tool.pylint.variables] -# List of additional names supposed to be defined in builtins. Remember that you -# should avoid defining new builtins when possible. -# additional-builtins = - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables = true - -# List of names allowed to shadow builtins -# allowed-redefined-builtins = - -# List of strings which can identify a callback function by name. A callback name -# must start or end with one of those strings. -callbacks = ["cb_", "_cb"] - -# A regular expression matching the name of dummy variables (i.e. expected to not -# be used). -dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" - -# Argument names that match this expression will be ignored. -ignored-argument-names = "_.*|^ignored_|^unused_" - -# Tells whether we should check for unused import in __init__ files. -# init-import = - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules = ["six.moves", "past.builtins", "future.builtins", "builtins", "io"] - - +[tool.ruff] +line-length = 120 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5c3e844..0000000 --- a/setup.cfg +++ /dev/null @@ -1,65 +0,0 @@ -[metadata] -name = objdictgen -version = attr: objdictgen.__version__ -description = CanFestival Object Dictionary tool -long_description = file: README.md -long_description_content_type = text/markdown -author = Svein Seldal -author_email = sveinse@seldal.com -url = https://github.com/Laerdal/python-objdictgen -keywords = build, development, canopen, canfestival, object dictionary, od -classifiers = - Development Status :: 4 - Beta - Intended Audience :: Developers - Topic :: Software Development :: Build Tools - Topic :: Software Development :: Code Generators - Topic :: Software Development :: Embedded Systems - Topic :: Software Development :: Libraries :: Application Frameworks - License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Programming Language :: Python :: 3 :: Only - -[options] -package_dir = - = src -packages = find_namespace: -include_package_data = True -python_requires = >=3.10, <4 -install_requires = - jsonschema - colorama - deepdiff - -[options.packages.find] -where = src - -[options.package_data] -objdictgen = py.typed -objdictgen.config = *.prf -objdictgen.img = * -objdictgen.schema = *.json - -[options.extras_require] -ui = - wxPython -dist = - build -dev = - pylint - isort - mypy - types-setuptools - types-colorama - types-wxpython - pytest - coverage - pytest-cov - pytest-mock - freezegun - -[options.entry_points] -console_scripts = - odg = objdictgen.__main__:main diff --git a/sonar-project.properties b/sonar-project.properties index 741820f..56a2cd5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,5 @@ sonar.projectKey=Laerdal_python-objdictgen sonar.organization=laerdal-foss sonar.python.coverage.reportPaths=coverage.xml -sonar.exclusions = tests/**, packaging/** +sonar.exclusions = packaging +sonar.tests = tests diff --git a/src/objdictgen/__init__.py b/src/objdictgen/__init__.py index 706666d..15c8378 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -1,6 +1,6 @@ """Object Dictionary tool for Canfestival, a CanOpen stack.""" # -# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C) 2022-2025 Svein Seldal, Laerdal Medical AS # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,6 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import importlib.metadata import os import sys from pathlib import Path @@ -24,9 +25,8 @@ from objdictgen.node import Node from objdictgen.nodemanager import NodeManager -__version__ = "3.5.3a1" -__version_tuple__ = (3, 5, 3, 1) -__copyright__ = "(c) 2024 Svein Seldal, Laerdal Medical AS, and several. Licensed under GPLv2.1." +__version__ = importlib.metadata.version("objdictgen") +__copyright__ = "(c) 2025 Svein Seldal, Laerdal Medical AS, and several. Licensed under GPLv2.1." # Shortcuts LoadFile = Node.LoadFile diff --git a/tests/test_installer.py b/tests/test_installer.py index c71a40c..8087644 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -18,7 +18,7 @@ def test_filereplacer(basepath, wd, setenvvar): tests = [ (1, "Test data", "Test data"), - (2, "@@{name}", "objdictgen"), + (2, "@@{Name}", "objdictgen"), (3, "@@{TEST}", "foobar"), # Read from the mocked environment variable (4, "@@{nonexisting}", "non-existing"), ] diff --git a/tests/test_objdictgen.py b/tests/test_objdictgen.py new file mode 100644 index 0000000..3db3b46 --- /dev/null +++ b/tests/test_objdictgen.py @@ -0,0 +1,11 @@ +""" General tests for objdictgen package. """ +import re + + +def test_import(): + """ Test that the package can be imported. """ + + import objdictgen + + assert re.match(r"^\d+\.\d+\.\d+([a-z]\d+)?$", objdictgen.__version__), \ + "Version string does not match expected format."