diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml new file mode 100644 index 0000000..ffd1522 --- /dev/null +++ b/.github/workflows/Test.yml @@ -0,0 +1,10 @@ +name: 'Tests' + +on: [push] + +jobs: + call_workflow: + uses: ./.github/workflows/Testbase.yml + with: + python: '3.11' + qt5: 'pyqt5' \ No newline at end of file diff --git a/.github/workflows/Testbase.yml b/.github/workflows/Testbase.yml new file mode 100644 index 0000000..989a2a0 --- /dev/null +++ b/.github/workflows/Testbase.yml @@ -0,0 +1,44 @@ +name: Base + +on: + workflow_call: + inputs: + python: + required: true + type: string + qt5: + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + env: + DISPLAY: ':99.0' + QT_DEBUG_PLUGINS: 1 + steps: + - name: Set up Python ${{ inputs.python }} + uses: actions/checkout@v4 + - name: Install dependencies + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python }} + - name: Install package + run: | + sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils + python -m pip install --upgrade pip + export QT_DEBUG_PLUGINS=1 + pip install flake8 pytest pytest-cov pytest-qt pytest-xdist pytest-xvfb setuptools wheel numpy h5py ${{ inputs.qt5 }} toml + pip install pymodaq pyqt5 + pip install -e . + - name: create local pymodaq folder and setting permissions + run: | + sudo mkdir /etc/.pymodaq + sudo chmod uo+rw /etc/.pymodaq + - name: Linting with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=src/pymodaq/resources/QtDesigner_Ressources,docs + - name: Test with pytest + run: | + pytest -n auto diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index cc1ffb5..69806c5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,19 +13,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine toml - - name: Build and publish + pip install hatch hatchling toml twine + - name: Get history and tags for SCM versioning to work + run: | + git branch + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + hatch version + - name: Build + run: hatch build + - name: Check the build + run: twine check dist/* + - name: publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }} + HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel - twine upload dist/* + hatch publish diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml new file mode 100644 index 0000000..a80cc91 --- /dev/null +++ b/.github/workflows/updater.yml @@ -0,0 +1,23 @@ +name: GitHub Actions Version Updater + +# Controls when the action will run. +on: + schedule: + # Automatically run at 00:00 on day-of-month 5. + - cron: '0 0 5 * *' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + # [Required] Access token with `workflow` scope. + token: ${{ secrets.WORKFLOW_SECRET }} + + - name: Run GitHub Actions Version Updater + uses: saadmk11/github-actions-version-updater@v0.8.1 + with: + # [Required] Access token with `workflow` scope. + token: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 0000000..a0208ee --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,10 @@ +from pathlib import Path +from hatchling.metadata.plugin.interface import MetadataHookInterface +from pymodaq_utils.resources.hatch_build_plugins import update_metadata_from_toml + +here = Path(__file__).absolute().parent + + +class PluginInfoTomlHook(MetadataHookInterface): + def update(self, metadata: dict) -> None: + update_metadata_from_toml(metadata, here) diff --git a/plugin_info.toml b/plugin_info.toml deleted file mode 100644 index 6537ae5..0000000 --- a/plugin_info.toml +++ /dev/null @@ -1,23 +0,0 @@ -## To modify by developper(s) of the plugin - -[plugin-info] -SHORT_PLUGIN_NAME = 'piezoconcept' #for instance daqmx -package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_piezoconcept' #to modify -description = 'Set of PyMoDAQ plugins for Actuators from Piezoconcept (Tested on the Bio200 XY stage. Include a version of the controller firmware emulating functions from PhysikInstrumente)' - -author = 'Sébastien Weber' -author-email = 'sebastien.weber@cemes.fr' -license = 'MIT' - -[plugin-install] -#packages required for your plugin: -packages-required = ['pyserial', 'pyvisa', 'pymodaq>=4.0.8'] -## - -[features] # defines the plugin features contained into this plugin -instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) -extensions = false # true if plugins contains dashboard extensions -pid_models = false # true if plugins contains pid models -h5exporters = false # true if plugin contains custom h5 file exporters -scanners = false # true if plugin contains custom scan layout (daq_scan extensions) - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5629ffc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[features] # defines the plugin features contained into this plugin +instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) +extensions = false # true if plugins contains dashboard extensions +models = false # true if plugins contains pid models +h5exporters = false # true if plugin contains custom h5 file exporters +scanners = false # true if plugin contains custom scan layout (daq_scan extensions) + +[urls] +package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_piezoconcept' + +[project] +name = "pymodaq_plugins_piezoconcept" +description = 'Set of PyMoDAQ plugins for Actuators from Piezoconcept (Tested on the Bio200 XY stage. Include a version of the controller firmware emulating functions from PhysikInstrumente)' + +dependencies = [ + "pymodaq>=5.0.0", + 'pyserial', + 'pyvisa', +] + +authors = [ + {name = "Sebastien J. Weber", email = "sebastien.weber@cemes.fr"}, +] +maintainers = [ + {name = "Sebastien J. Weber", email = "sebastien.weber@cemes.fr"}, +] + +# nottodo: leave everything below as is! + +dynamic = ["version", "urls", "entry-points"] +readme = "README.rst" +license = { file="LICENSE" } +requires-python = ">=3.8" + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Human Machine Interfaces", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: User Interfaces", +] + +[build-system] +requires = [ + "hatchling>=1.9.0", + "hatch-vcs", "toml", + "pymodaq_utils>=0.0.6", +] +build-backend = "hatchling.build" + +[tool.hatch.metadata.hooks.custom] + +[tool.hatch.version] +source = "vcs" + diff --git a/setup.py b/setup.py deleted file mode 100644 index 6cb1a84..0000000 --- a/setup.py +++ /dev/null @@ -1,64 +0,0 @@ -from setuptools import setup, find_packages -import toml - -config = toml.load('./plugin_info.toml') -SHORT_PLUGIN_NAME = config['plugin-info']['SHORT_PLUGIN_NAME'] -PLUGIN_NAME = f"pymodaq_plugins_{config['plugin-info']['SHORT_PLUGIN_NAME']}" - - -from pathlib import Path - -with open(str(Path(__file__).parent.joinpath(f'src/{PLUGIN_NAME}/resources/VERSION')), 'r') as fvers: - version = fvers.read().strip() - - -with open('README.rst') as fd: - long_description = fd.read() - -setupOpts = dict( - name=PLUGIN_NAME, - description=config['plugin-info']['description'], - long_description=long_description, - license=config['plugin-info']['license'], - url=config['plugin-info']['package-url'], - author=config['plugin-info']['author'], - author_email=config['plugin-info']['author-email'], - classifiers=[ - "Programming Language :: Python :: 3", - "Development Status :: 5 - Production/Stable", - "Environment :: Other Environment", - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering :: Human Machine Interfaces", - "Topic :: Scientific/Engineering :: Visualization", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Software Development :: User Interfaces", - ], ) - - -entrypoints = {} -if 'features' in config: - if config['features'].get('instruments', False): - entrypoints['pymodaq.instruments'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('extensions', False): - entrypoints['pymodaq.extensions'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('pid_models', False): - entrypoints['pymodaq.pid_models'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('h5exporters', False): - entrypoints['pymodaq.h5exporters'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - if config['features'].get('scanners', False): - entrypoints['pymodaq.scanners'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' -else: - entrypoints['pymodaq.instruments'] = f'{SHORT_PLUGIN_NAME} = {PLUGIN_NAME}' - -setup( - version=version, - packages=find_packages(where='./src'), - package_dir={'': 'src'}, - include_package_data=True, - entry_points=entrypoints, - install_requires=['toml', ]+config['plugin-install']['packages-required'], - **setupOpts -) - diff --git a/src/pymodaq_plugins_piezoconcept/__init__.py b/src/pymodaq_plugins_piezoconcept/__init__.py index 561fcac..9b1abfe 100644 --- a/src/pymodaq_plugins_piezoconcept/__init__.py +++ b/src/pymodaq_plugins_piezoconcept/__init__.py @@ -1,5 +1,13 @@ from pathlib import Path -from pymodaq.utils.daq_utils import set_logger +from .utils import Config +from pymodaq_utils.utils import get_version, PackageNotFoundError +from pymodaq_utils.logger import set_logger, get_module_name + +config = Config() +try: + __version__ = get_version(__package__) +except PackageNotFoundError: + __version__ = '0.0.0dev' + + -with open(str(Path(__file__).parent.joinpath('resources/VERSION')), 'r') as fvers: - __version__ = fvers.read().strip() \ No newline at end of file diff --git a/src/pymodaq_plugins_piezoconcept/daq_move_plugins/daq_move_PiezoConcept.py b/src/pymodaq_plugins_piezoconcept/daq_move_plugins/daq_move_PiezoConcept.py index abceffc..a4e426a 100644 --- a/src/pymodaq_plugins_piezoconcept/daq_move_plugins/daq_move_PiezoConcept.py +++ b/src/pymodaq_plugins_piezoconcept/daq_move_plugins/daq_move_PiezoConcept.py @@ -1,6 +1,6 @@ from qtpy.QtCore import QThread from pymodaq.control_modules.move_utility_classes import DAQ_Move_base, comon_parameters_fun, main -from pymodaq.utils.daq_utils import ThreadCommand + from pymodaq_plugins_piezoconcept.hardware.piezoconcept.piezoconcept import PiezoConcept, Position, Time from pymodaq_plugins_piezoconcept.utils import Config diff --git a/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/piezo_test.py b/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/piezo_test.py deleted file mode 100644 index 6e86b3a..0000000 --- a/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/piezo_test.py +++ /dev/null @@ -1,24 +0,0 @@ -from pymodaq_plugins.hardware.piezoconcept.piezoconcept import PiezoConcept, Position, Time -import numpy as np - -if __name__ == '__main__': - - controller = PiezoConcept() - controller.init_communication('COM6') - print(controller.get_controller_infos()) - controller.get_time_interval() - xaxis = np.linspace(100000, 120000, 21) - yaxis = np.linspace(100000, 110000, 11) - zaxis = np.linspace(0, 0, 11) - - #controller.set_positions_simple(xaxis, yaxis, []) - #controller.run_simple() - #controller._get_read() - xaxis = np.linspace(100000, 120000, 21) - yaxis = np.linspace(100000, 110000, 21) - #controller.set_positions_arbitrary([xaxis, yaxis]) - #controller.run_arbitrary() - #controller._get_read() - controller.get_TTL_state(1) - controller._get_read() - pass \ No newline at end of file diff --git a/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/run_arbitrary.py b/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/run_arbitrary.py deleted file mode 100644 index 88d583d..0000000 --- a/src/pymodaq_plugins_piezoconcept/hardware/piezoconcept/run_arbitrary.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Mar 14 14:54:55 2019 - -@author: flim-users -""" -import time -import numpy as np -from pymodaq_plugins.hardware.piezoconcept.piezoconcept import PiezoConcept, Position, Time -from pymodaq.daq_utils.daq_utils import set_scan_linear -#%% - -stage = PiezoConcept() - -stage.init_communication('COM6') -print(stage.get_controller_infos()) -stage._write_command('CHAIO 1o2s') -stage._get_read() - - -#%% -stage.set_time_interval(Time(20,'m')) -stage.get_time_interval() -#%% -stage.get_position('X') -#%% -stage.get_position('Y') -#%% -offset = 100000 - -start_axis1 = -10000 +offset -start_axis2 = -10000 +offset -stop_axis1 = 10000 +offset -stop_axis2 = 10000 +offset -step_axis1 = 2000 -step_axis2 = 2000 - -scan_params = set_scan_linear(start_axis1,start_axis2,stop_axis1,stop_axis2,step_axis1,step_axis2,back_and_force=True) -positions = scan_params.positions -#%% -stage.move_axis(pos = Position(pos = offset, unit = 'n')) -stage.move_axis(pos = Position(axis = 'Y', pos = offset, unit = 'n')) -stage.set_positions_arbitrary(positions) - -#%% -for ind_run in range(1): - stage.run_arbitrary() - info= '' - while 'Completed' not in info: - time.sleep(0.1) - info = stage._get_read() - print(info) - print(info) -#%% -stage.close_communication() diff --git a/src/pymodaq_plugins_piezoconcept/resources/VERSION b/src/pymodaq_plugins_piezoconcept/resources/VERSION deleted file mode 100644 index 9084fa2..0000000 --- a/src/pymodaq_plugins_piezoconcept/resources/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.1.0 diff --git a/src/pymodaq_plugins_piezoconcept/utils.py b/src/pymodaq_plugins_piezoconcept/utils.py index 1f225ab..4981b95 100644 --- a/src/pymodaq_plugins_piezoconcept/utils.py +++ b/src/pymodaq_plugins_piezoconcept/utils.py @@ -6,7 +6,7 @@ """ from pathlib import Path -from pymodaq.utils.config import BaseConfig, USER +from pymodaq_utils.config import BaseConfig, USER class Config(BaseConfig): diff --git a/tests/test_plugin_package_structure.py b/tests/test_plugin_package_structure.py new file mode 100644 index 0000000..4b51d06 --- /dev/null +++ b/tests/test_plugin_package_structure.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +Created the 17/10/2023 + +@author: Sebastien Weber +""" +import pytest +from pathlib import Path +import importlib +import pkgutil +from collections.abc import Iterable + +from pymodaq_data import Q_, Unit + + +MANDATORY_MOVE_METHODS = ['ini_attributes', 'get_actuator_value', 'close', 'commit_settings', + 'ini_stage', 'move_abs', 'move_home', 'move_rel', 'stop_motion'] +MANDATORY_VIEWER_METHODS = ['ini_attributes', 'grab_data', 'close', 'commit_settings', + 'ini_detector', ] + + +def get_package_name(): + here = Path(__file__).parent + package_name = here.parent.stem + return package_name + +def get_move_plugins(): + pkg_name = get_package_name() + try: + move_mod = importlib.import_module(f'{pkg_name}.daq_move_plugins') + plugin_list = [mod for mod in [mod[1] for mod in + pkgutil.iter_modules([str(move_mod.path.parent)])] + if 'daq_move_' in mod] + except ModuleNotFoundError: + plugin_list = [] + move_mod = None + return plugin_list, move_mod + + +def get_viewer_plugins(dim='0D'): + pkg_name = get_package_name() + try: + viewer_mod = importlib.import_module(f'{pkg_name}.daq_viewer_plugins.plugins_{dim}') + + plugin_list = [mod for mod in [mod[1] for mod in + pkgutil.iter_modules([str(viewer_mod.path.parent)])] + if f'daq_{dim}viewer_' in mod] + except ModuleNotFoundError: + plugin_list = [] + viewer_mod = None + return plugin_list, viewer_mod + + +def test_package_name_ok(): + assert 'pymodaq_plugins_' in get_package_name()[0:16] + + +def test_imports(): + pkg_name = get_package_name() + mod = importlib.import_module(pkg_name) + assert hasattr(mod, 'config') + assert hasattr(mod, '__version__') + move_mod = importlib.import_module(f'{pkg_name}', 'daq_move_plugins') + importlib.import_module(f'{pkg_name}', 'daq_viewer_plugins') + importlib.import_module(f'{pkg_name}', 'extensions') + importlib.import_module(f'{pkg_name}', 'models') + importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_0D') + importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_1D') + importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_2D') + importlib.import_module(f'{pkg_name}.daq_viewer_plugins', 'plugins_ND') + + +def test_move_inst_plugins_name(): + plugin_list, move_mod = get_move_plugins() + for plug in plugin_list: + name = plug.split('daq_move_')[1] + assert hasattr(getattr(move_mod, plug), f'DAQ_Move_{name}') + + +def test_move_has_mandatory_methods(): + plugin_list, move_mod = get_move_plugins() + for plug in plugin_list: + name = plug.split('daq_move_')[1] + klass = getattr(getattr(move_mod, plug), f'DAQ_Move_{name}') + for meth in MANDATORY_MOVE_METHODS: + assert hasattr(klass, meth) + + +def test_move_has_correct_units(): + plugin_list, move_mod = get_move_plugins() + for plug in plugin_list: + name = plug.split('daq_move_')[1] + klass = getattr(getattr(move_mod, plug), f'DAQ_Move_{name}') + if not isinstance(klass._controller_units, list): + units = [klass._controller_units] + else: + units = klass._controller_units + for unit in units: + Unit(unit) # check if the unit is known from pint + + +@pytest.mark.parametrize('dim', ('0D', '1D', '2D', 'ND')) +def test_viewer_has_mandatory_methods(dim): + plugin_list, mod = get_viewer_plugins(dim) + for plug in plugin_list: + name = plug.split(f'daq_{dim}viewer_')[1] + try: + module = importlib.import_module(f'.{plug}', mod.__package__) + except Exception: + break + klass = getattr(module, f'DAQ_{dim}Viewer_{name}') + for meth in MANDATORY_VIEWER_METHODS: + assert hasattr(klass, meth)