diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cea71dc23..963e65416 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build_macos +name: pyinstaller_build on: [push, pull_request] @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.12"] steps: @@ -18,23 +18,25 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install packages + - name: Install linux libraries + shell: bash + if: ${{ matrix.os == 'ubuntu-latest' }} run: | - pip install -r requirements_exe_build.txt - python -m build --wheel + sudo apt update + sudo apt install libegl1 libopengl0 - - name: Build an executable - shell: bash - if: ${{ matrix.python-version == '3.12' }} + - name: Install Python packages + run: | + pip install tox + + - name: Build the executable run: | - pip install -r requirements_exe_build.txt - python -m build --wheel - pyinstaller asammdf.spec --distpath dist/${RUNNER_OS} --noconfirm --clean + tox -e pyinstaller -- --distpath dist/${{ matrix.os }} --noconfirm --clean # see: https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - name: Archive dist artifacts uses: actions/upload-artifact@v4 with: - name: dist - path: dist/** + name: pyinstaller-${{ matrix.os }} + path: dist/${{ matrix.os }}/** if-no-files-found: error diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml deleted file mode 100644 index 89dc5255d..000000000 --- a/.github/workflows/build_linux.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: build_linux - -on: [push, pull_request] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.10"] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install packages - run: | - pip install -r requirements_exe_build.txt - python -m build --wheel - - - name: Build an executable - shell: bash - if: ${{ matrix.python-version == '3.10' }} - run: | - sudo apt update - sudo apt install libegl1 libopengl0 - pyinstaller asammdf.spec --distpath dist/${RUNNER_OS} --noconfirm --clean - - # see: https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - - name: Archive dist artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/** - if-no-files-found: error diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml deleted file mode 100644 index 3e608810f..000000000 --- a/.github/workflows/build_windows.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: build_windows - -on: [push, pull_request] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest] - python-version: ["3.12"] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install packages - run: | - pip install -r requirements_exe_build.txt - python -m build --wheel - - - name: Build an executable - shell: bash - if: ${{ matrix.python-version == '3.12' }} - run: | - pip install -r requirements_exe_build.txt - python -m build --wheel - pyinstaller asammdf.spec --distpath dist/${RUNNER_OS} --noconfirm --clean - - # see: https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - - name: Archive dist artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/** - if-no-files-found: error diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..d6fa4bd85 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.15...3.26) + +project( + ${SKBUILD_PROJECT_NAME} + LANGUAGES C + VERSION ${SKBUILD_PROJECT_VERSION}) + +find_package( + Python + COMPONENTS + Interpreter + Development.Module + ${SKBUILD_SABI_COMPONENT} + NumPy + REQUIRED) + +python_add_library(cutils + MODULE + src/asammdf/blocks/cutils.c + WITH_SOABI USE_SABI 3.9) + +target_link_libraries(cutils PRIVATE Python::NumPy) + +install(TARGETS cutils DESTINATION "asammdf/blocks") diff --git a/pyproject.toml b/pyproject.toml index 848dacafd..c6b9c0a14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,84 @@ [build-system] -requires = ["numpy", "setuptools"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core", "numpy"] +build-backend = "scikit_build_core.build" + +[project] +name = "asammdf" +description="ASAM MDF measurement data file parser" +readme = "README.md" +requires-python = ">=3.9" +license = { text = "LGPLv3+" } +authors = [{ name = "Daniel Hrisca", email = "daniel.hrisca@gmail.com" }] +dynamic = ["version"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +keywords = [ + "read", + "reader", + "edit", + "editor", + "parse", + "parser", + "asam", + "mdf", + "measurement", +] +dependencies = [ + "canmatrix[arxml,dbc]>=1.0", + "isal; platform_machine == 'x86_64' or platform_machine == 'AMD64'", + "lxml>=4.9.3", + "lz4", + "numexpr", + "numpy>=1.23.0", + "pandas", + "python-dateutil", + "typing-extensions", +] + +[project.optional-dependencies] +decode = ["faust-cchardet==2.1.19", "chardet"] +export = [ + "pyarrow", + "h5py", + "hdf5storage>=0.1.19", + "python-snappy", +] +export_matlab_v5 = ["scipy"] +gui = [ + "natsort", + "PySide6", + "pyqtgraph", + "pyqtlet2", + "packaging", + "QtPy", +] +encryption = ["cryptography", "keyring"] +symbolic_math = ["sympy"] +filesystem = ["fsspec"] + +[project.scripts] +asammdf = "asammdf.app.asammdfgui:main [gui]" + +[project.urls] +Documentation = "https://asammdf.readthedocs.io/en/master" +Issues = "https://github.com/danielhrisca/asammdf/issues" +Source = "https://github.com/danielhrisca/asammdf" + +[tool.scikit-build] +metadata.version.provider = "scikit_build_core.metadata.regex" +metadata.version.input = "src/asammdf/version.py" +wheel.packages = ["src/asammdf"] +wheel.py-api = "cp39" [tool.black] line-length = 120 diff --git a/requirements_exe_build.txt b/requirements_exe_build.txt deleted file mode 100644 index b305c6596..000000000 --- a/requirements_exe_build.txt +++ /dev/null @@ -1,5 +0,0 @@ ---editable .[decode,encryption,export,export_matlab_v5,gui,symbolic_math] -build -cmerg -pyopengl -pyinstaller diff --git a/setup.py b/setup.py deleted file mode 100644 index 095389893..000000000 --- a/setup.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -asammdf - -""" - -from pathlib import Path - -from numpy import get_include -from setuptools import Extension, find_packages, setup - -PROJECT_PATH = Path(__file__).parent - - -def _get_version(): - with PROJECT_PATH.joinpath("src", "asammdf", "version.py").open() as f: - line = next(line for line in f if line.startswith("__version__")) - - version = line.partition("=")[2].strip()[1:-1] - - return version - - -def _get_long_description(): - description = PROJECT_PATH.joinpath("README.md").read_text(encoding="utf-8") - - return description - - -def _get_ext_modules(): - modules = [ - Extension( - "asammdf.blocks.cutils", - ["src/asammdf/blocks/cutils.c"], - include_dirs=[get_include()], - extra_compile_args=["-std=c99"], - ) - ] - - return modules - - -setup( - name="asammdf", - # Versions should comply with PEP440. For a discussion on single-sourcing - # the version across setup.py and the project code, see - # https://packaging.python.org/en/latest/single_source_version.html - version=_get_version(), - description="ASAM MDF measurement data file parser", - long_description=_get_long_description(), - long_description_content_type=r"text/markdown", - # The project's main homepage. - url="https://github.com/danielhrisca/asammdf", - # Author details - author="Daniel Hrisca", - author_email="daniel.hrisca@gmail.com", - # Choose your license - license="LGPLv3+", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - "Development Status :: 5 - Production/Stable", - # Indicate who your project is intended for - "Intended Audience :: Developers", - "Topic :: Software Development", - "Topic :: Scientific/Engineering", - # Pick your license as you wish (should match "license" above) - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - ], - # Supported python versions - python_requires=">=3.9", - # What does your project relate to? - keywords="read reader edit editor parse parser asam mdf measurement", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - packages=find_packages("src"), - package_dir={"": "src"}, - # Alternatively, if you want to distribute just a my_module.py, uncomment - # this: - # py_modules=["my_module"], - # List run-time dependencies here. These will be installed by pip when - # your project is installed. For an analysis of "install_requires" vs pip's - # requirements files see: - # https://packaging.python.org/en/latest/requirements.html - install_requires=[ - "canmatrix[arxml,dbc]>=1.0", - "isal; platform_machine == 'x86_64' or platform_machine == 'AMD64'", - "lxml>=4.9.3", - "lz4", - "numexpr", - "numpy>=1.23.0", - "pandas", - "python-dateutil", - "typing-extensions", - ], - # List additional groups of dependencies here (e.g. development - # dependencies). You can install these using the following syntax, - # for example: - # $ pip install -e .[dev,test] - extras_require={ - "decode": ["faust-cchardet==2.1.19", "chardet"], - "export": [ - "pyarrow", - "h5py", - "hdf5storage>=0.1.19", - "python-snappy", - ], - "export_matlab_v5": "scipy", - "gui": [ - "natsort", - "PySide6", - "pyqtgraph", - "pyqtlet2", - "packaging", - "QtPy", - ], - "encryption": ["cryptography", "keyring"], - "symbolic_math": "sympy", - "filesystem": "fsspec", - }, - # If there are data files included in your packages that need to be - # installed, specify them here. If using Python 2.6 or less, then these - # have to be included in MANIFEST.in as well. - package_data={"asammdf.gui.ui": ["*.ui"]}, - include_package_data=True, - # Although 'package_data' is the preferred approach, in some case you may - # need to place data files outside of your packages. See: - # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files - # In this case, 'data_file' will be installed into '/my_data' - # data_files=[('my_data', ['data/data_file'])], - # To provide executable scripts, use entry points in preference to the - # "scripts" keyword. Entry points provide cross-platform support and allow - # pip to create the appropriate form of executable for the target platform. - entry_points={"console_scripts": ["asammdf = asammdf.app.asammdfgui:main [gui]"]}, - ext_modules=_get_ext_modules(), -) diff --git a/src/asammdf/blocks/cutils.c b/src/asammdf/blocks/cutils.c index 1525af0de..3f058bcd0 100644 --- a/src/asammdf/blocks/cutils.c +++ b/src/asammdf/blocks/cutils.c @@ -1780,7 +1780,7 @@ static PyObject *bytes_dtype_size(PyObject *self, PyObject *args) break; } - current_size = PyBytes_GET_SIZE(*pointer); + current_size = PyBytes_Size(*pointer); if (current_size > size) size = current_size; } diff --git a/src/asammdf/blocks/v2_v3_blocks.py b/src/asammdf/blocks/v2_v3_blocks.py index 59dc31e63..45732e4e0 100644 --- a/src/asammdf/blocks/v2_v3_blocks.py +++ b/src/asammdf/blocks/v2_v3_blocks.py @@ -2729,7 +2729,7 @@ def __init__(self, **kwargs) -> None: self.dg_nr = 0 try: user = getuser() - except ModuleNotFoundError: + except (ModuleNotFoundError, OSError): user = "" self.author_field = f"{user:\0<32}".encode("latin-1") self.department_field = "{:\0<32}".format("").encode("latin-1") diff --git a/src/asammdf/gui/widgets/formated_axis.py b/src/asammdf/gui/widgets/formated_axis.py index 40f190c0a..16f3fcf26 100644 --- a/src/asammdf/gui/widgets/formated_axis.py +++ b/src/asammdf/gui/widgets/formated_axis.py @@ -137,13 +137,14 @@ def tickStrings(self, values, scale, spacing): strns = [str(timedelta(seconds=val)) for val in values] elif self.format == "date": strns = ( - pd.to_datetime( - np.array(values) + self.origin.timestamp(), - unit="s", - errors="coerce", + ( + pd.to_timedelta( + np.array(values), + unit="s", + errors="coerce", + ) + + self.origin ) - .tz_localize("UTC") - .tz_convert(LOCAL_TIMEZONE) .astype(str) .to_list() ) diff --git a/src/asammdf/gui/widgets/plot.py b/src/asammdf/gui/widgets/plot.py index a94d0686a..a9baac670 100644 --- a/src/asammdf/gui/widgets/plot.py +++ b/src/asammdf/gui/widgets/plot.py @@ -10,6 +10,7 @@ from traceback import format_exc from zipfile import ZIP_DEFLATED, ZipFile +import dateutil.tz import numpy as np import pyqtgraph as pg from pyqtgraph import Qt @@ -17,6 +18,7 @@ from PySide6 import QtCore, QtGui, QtWidgets PLOT_BUFFER_SIZE = 4000 +LOCAL_TIMEZONE = dateutil.tz.tzlocal() from ... import tool as Tool @@ -3682,7 +3684,7 @@ def __init__( else: fmt = "phys" self.x_axis.format = fmt - self.x_axis.origin = origin + self.x_axis.origin = origin.astimezone(LOCAL_TIMEZONE) self.y_axis = FormatedAxis( "left", maxTickLength=-5, background=self.backgroundBrush().color(), linked_signal=(self, None) diff --git a/src/asammdf/gui/widgets/tabular_base.py b/src/asammdf/gui/widgets/tabular_base.py index 27cec5a58..172ea8f40 100644 --- a/src/asammdf/gui/widgets/tabular_base.py +++ b/src/asammdf/gui/widgets/tabular_base.py @@ -1509,7 +1509,6 @@ def to_config(self): return config def time_as_date_changed(self, state): - s = self.start count = self.filters.count() if state == QtCore.Qt.CheckState.Checked: diff --git a/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py b/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py index 6b35b410a..a0b61e35b 100644 --- a/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py +++ b/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py @@ -3,6 +3,8 @@ from json import JSONDecodeError import pathlib import re +import sys +import unittest from unittest import mock from unittest.mock import ANY @@ -844,6 +846,7 @@ def test_Action_SetRandomColor(self): self.assertNotEqual(previous_c_color, current_c_color) self.assertNotEqual(current_b_color, current_c_color) + @unittest.skipIf(sys.platform == "win32", "times out on Windows") def test_Action_CopyDisplayProperties_Group(self): """ Test Scope: @@ -1044,6 +1047,7 @@ def test_Action_PasteDisplayProperties_Group(self): # Evaluate self.assertEqual(group_channel_a_properties, group_channel_b_properties) + @unittest.skipIf(sys.platform == "win32", "times out on Windows") def test_Action_CopyChannelStructure_Group(self): """ Test Scope: diff --git a/test/asammdf/gui/widgets/plot/test_PlotWidget_DoubleClick.py b/test/asammdf/gui/widgets/plot/test_PlotWidget_DoubleClick.py index 09c408781..74e59fd45 100644 --- a/test/asammdf/gui/widgets/plot/test_PlotWidget_DoubleClick.py +++ b/test/asammdf/gui/widgets/plot/test_PlotWidget_DoubleClick.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +import sys +import unittest from unittest import mock from PySide6 import QtCore, QtGui, QtTest @@ -174,6 +176,7 @@ def test_EnableDisable_Group(self): msg=f"Color of channel {plot_channel_0.text(self.Column.NAME)} is not present on plot.", ) + @unittest.skipIf(sys.platform == "win32", "fails on Windows") def test_EnableDisable_ParentGroup(self): """ Test Scope: @@ -312,6 +315,7 @@ def test_EnableDisable_ParentGroup(self): msg=f"Color for Channel: {channel.text(self.Column.NAME)} not present on 'plot'", ) + @unittest.skipIf(sys.platform == "win32", "fails on Windows") def test_EnableDisable_Subgroup(self): """ Test Scope: diff --git a/tox.ini b/tox.ini index 7406b9c32..c9cf038f5 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,21 @@ deps = commands = sphinx-build --builder html --nitpicky doc doc/_build/html +[testenv:pyinstaller] +base = +deps = + pyinstaller + pyopengl +extras = + decode + encryption + export + export_matlab_v5 + gui + symbolic_math +commands = + pyinstaller asammdf.spec {posargs} + [gh-actions] python = 3.9: py39, black, ruff, doc