diff --git a/.github/workflows/pytest-matrix.yml b/.github/workflows/pytest-matrix.yml index 4a1e4852..9446714c 100644 --- a/.github/workflows/pytest-matrix.yml +++ b/.github/workflows/pytest-matrix.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.11", "3.12", "3.13"] env: BEC_WIDGETS_BRANCH: main # Set the branch you want for bec_widgets diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 5b2785ac..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,289 +0,0 @@ -# This file is a template, and might need editing before it works on your project. -# Official language image. Look for the different tagged releases at: -# https://hub.docker.com/r/library/python/tags/ -image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11 -#commands to run in the Docker container before starting each job. -variables: - DOCKER_TLS_CERTDIR: "" - BEC_CORE_BRANCH: - description: bec branch - value: main - OPHYD_DEVICES_BRANCH: - description: ophyd_devices branch - value: main - CHILD_PIPELINE_BRANCH: $CI_DEFAULT_BRANCH - CHECK_PKG_VERSIONS: - description: Whether to run additional tests against min/max/random selection of dependencies. Set to 1 for running. - value: 0 - -workflow: - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" - - if: $CI_PIPELINE_SOURCE == "web" - - if: $CI_PIPELINE_SOURCE == "pipeline" - - if: $CI_PIPELINE_SOURCE == "parent_pipeline" - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS - when: never - - if: $CI_COMMIT_BRANCH - -include: - - template: Security/Secret-Detection.gitlab-ci.yml - - project: "bec/awi_utils" - file: "/templates/check-packages-job.yml" - inputs: - stage: test - path: "." - pytest_args: "-v,--random-order,tests/unit_tests" - pip_args: ".[dev]" - -# different stages in the pipeline -stages: - - Formatter - - test - - AdditionalTests - - End2End - - Deploy - -.install-qt-webengine-deps: &install-qt-webengine-deps - - apt-get -y install libnss3 libxdamage1 libasound2 libatomic1 libxcursor1 - - export QTWEBENGINE_DISABLE_SANDBOX=1 - -.clone-repos: &clone-repos - - echo -e "\033[35;1m Using branch $BEC_CORE_BRANCH of BEC CORE \033[0;m"; - - git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git - - echo -e "\033[35;1m Using branch $OPHYD_DEVICES_BRANCH of OPHYD_DEVICES \033[0;m"; - - git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git - - export OHPYD_DEVICES_PATH=$PWD/ophyd_devices - -.install-repos: &install-repos - - pip install -e ./ophyd_devices - - pip install -e ./bec/bec_lib[dev] - - pip install -e ./bec/bec_ipython_client - - pip install -e ./bec/pytest_bec_e2e - -.install-os-packages: &install-os-packages - - apt-get update - - apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb - - *install-qt-webengine-deps - -before_script: - - if [[ "$CI_PROJECT_PATH" != "bec/bec_widgets" ]]; then - echo -e "\033[35;1m Using branch $CHILD_PIPELINE_BRANCH of BEC Widgets \033[0;m"; - test -d bec_widgets || git clone --branch $CHILD_PIPELINE_BRANCH https://gitlab.psi.ch/bec/bec_widgets.git; cd bec_widgets; - fi - -formatter: - stage: Formatter - needs: [] - script: - - pip install -e ./[dev] - - isort --check --diff --line-length=100 --profile=black --multi-line=3 --trailing-comma ./ - - black --check --diff --color --line-length=100 --skip-magic-trailing-comma ./ - rules: - - if: $CI_PROJECT_PATH == "bec/bec_widgets" - -pylint: - stage: Formatter - needs: [] - before_script: - - pip install pylint pylint-exit anybadge - - pip install -e .[dev] - script: - - mkdir ./pylint - - pylint ./bec_widgets --output-format=text --output=./pylint/pylint.log | tee ./pylint/pylint.log || pylint-exit $? - - PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log) - - anybadge --label=Pylint --file=pylint/pylint.svg --value=$PYLINT_SCORE 2=red 4=orange 8=yellow 10=green - - echo "Pylint score is $PYLINT_SCORE" - artifacts: - paths: - - ./pylint/ - expire_in: 1 week - rules: - - if: $CI_PROJECT_PATH == "bec/bec_widgets" - -pylint-check: - stage: Formatter - needs: [] - allow_failure: true - before_script: - - pip install pylint pylint-exit anybadge - - apt-get update - - apt-get install -y bc - script: - - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME - # Identify changed Python files - - if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then - TARGET_BRANCH_COMMIT_SHA=$(git rev-parse origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME); - CHANGED_FILES=$(git diff --name-only $TARGET_BRANCH_COMMIT_SHA HEAD | grep '\.py$' || true); - else - CHANGED_FILES=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '\.py$' || true); - fi - - if [ -z "$CHANGED_FILES" ]; then echo "No Python files changed."; exit 0; fi - - - echo "Changed Python files:" - - $CHANGED_FILES - # Run pylint only on changed files - - mkdir ./pylint - - pylint $CHANGED_FILES --output-format=text | tee ./pylint/pylint_changed_files.log || pylint-exit $? - - PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint_changed_files.log) - - echo "Pylint score is $PYLINT_SCORE" - - # Fail the job if the pylint score is below 9 - - if [ "$(echo "$PYLINT_SCORE < 9" | bc)" -eq 1 ]; then echo "Your pylint score is below the acceptable threshold (9)."; exit 1; fi - artifacts: - paths: - - ./pylint/ - expire_in: 1 week - rules: - - if: $CI_PROJECT_PATH == "bec/bec_widgets" - -tests: - stage: test - needs: [] - variables: - QT_QPA_PLATFORM: "offscreen" - script: - - *clone-repos - - *install-os-packages - - *install-repos - - pip install -e .[dev,pyside6] - - coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --maxfail=2 --random-order --full-trace ./tests/unit_tests - - coverage report - - coverage xml - coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' - artifacts: - reports: - junit: report.xml - coverage_report: - coverage_format: cobertura - path: coverage.xml - paths: - - tests/reference_failures/ - when: always - -generate-client-check: - stage: test - needs: [] - variables: - QT_QPA_PLATFORM: "offscreen" - script: - - *clone-repos - - *install-os-packages - - *install-repos - - pip install -e .[dev,pyside6] - - bw-generate-cli --target bec_widgets - # if there are changes in the generated files, fail the job - - git diff --exit-code - -test-matrix: - parallel: - matrix: - - PYTHON_VERSION: - - "3.10" - - "3.11" - - "3.12" - QT_PCKG: - - "pyside6" - - stage: AdditionalTests - needs: [] - variables: - QT_QPA_PLATFORM: "offscreen" - PYTHON_VERSION: "" - QT_PCKG: "" - image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:$PYTHON_VERSION - script: - - *clone-repos - - *install-os-packages - - *install-repos - - pip install -e .[dev,$QT_PCKG] - - pytest -v --maxfail=2 --junitxml=report.xml --random-order ./tests/unit_tests - -end-2-end-conda: - stage: End2End - needs: [] - image: continuumio/miniconda3:25.1.1-2 - allow_failure: false - variables: - QT_QPA_PLATFORM: "offscreen" - script: - - *clone-repos - - *install-os-packages - - conda config --show-sources - - conda config --add channels conda-forge - - conda config --system --remove channels https://repo.anaconda.com/pkgs/main - - conda config --system --remove channels https://repo.anaconda.com/pkgs/r - - conda config --remove channels https://repo.anaconda.com/pkgs/main - - conda config --remove channels https://repo.anaconda.com/pkgs/r - - conda config --show-sources - - conda config --set channel_priority strict - - conda config --set always_yes yes --set changeps1 no - - conda create -q -n test-environment python=3.11 - - conda init bash - - source ~/.bashrc - - conda activate test-environment - - - cd ./bec - - source ./bin/install_bec_dev.sh -t - - cd ../ - - pip install -e ./ophyd_devices - - - pip install -e .[dev,pyside6] - - pytest -v --files-path ./ --start-servers --random-order ./tests/end-2-end - - artifacts: - when: on_failure - paths: - - ./logs/*.log - expire_in: 1 week - - rules: - - if: '$CI_PIPELINE_SOURCE == "schedule"' - - if: '$CI_PIPELINE_SOURCE == "web"' - - if: '$CI_PIPELINE_SOURCE == "pipeline"' - - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"' - - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"' - - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"' - - if: "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^pre_release.*$/" - -semver: - stage: Deploy - needs: ["tests"] - script: - - git config --global user.name "ci_update_bot" - - git config --global user.email "ci_update_bot@bec.ch" - - git checkout "$CI_COMMIT_REF_NAME" - - git reset --hard origin/"$CI_COMMIT_REF_NAME" - - # delete all local tags - - git tag -l | xargs git tag -d - - git fetch --tags - - git tag - - # build and publish package - - pip install python-semantic-release==9.* wheel build twine - - export GL_TOKEN=$CI_UPDATES - - semantic-release -vv version - - # check if any artifacts were created - - if [ ! -d dist ]; then echo No release will be made; exit 0; fi - - twine upload dist/* -u __token__ -p $CI_PYPI_TOKEN --skip-existing - - semantic-release publish - - allow_failure: false - rules: - - if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/bec_widgets"' - -pages: - stage: Deploy - needs: ["semver"] - variables: - TARGET_BRANCH: $CI_COMMIT_REF_NAME - rules: - - if: "$CI_COMMIT_TAG != null" - variables: - TARGET_BRANCH: $CI_COMMIT_TAG - - if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/bec_widgets"' - script: - - curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/bec-widgets/253243/ diff --git a/.pylintrc b/.pylintrc index c5dbbc15..d9bca4c8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -52,7 +52,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.10 +py-version=3.11 # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. diff --git a/README.md b/README.md index cf48ff18..e8cf0d07 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![badge](https://img.shields.io/pypi/v/bec-widgets)](https://pypi.org/project/bec-widgets/) [![License](https://img.shields.io/github/license/bec-project/bec_widgets)](./LICENSE) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue?logo=python&logoColor=white)](https://www.python.org) +[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue?logo=python&logoColor=white)](https://www.python.org) [![PySide6](https://img.shields.io/badge/PySide6-blue?logo=qt&logoColor=white)](https://doc.qt.io/qtforpython/) [![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) [![codecov](https://codecov.io/gh/bec-project/bec_widgets/graph/badge.svg?token=0Z9IQRJKMY)](https://codecov.io/gh/bec-project/bec_widgets) diff --git a/bec_widgets/utils/forms_from_types/items.py b/bec_widgets/utils/forms_from_types/items.py index 73e702b3..e9d92db6 100644 --- a/bec_widgets/utils/forms_from_types/items.py +++ b/bec_widgets/utils/forms_from_types/items.py @@ -4,7 +4,17 @@ import typing from abc import abstractmethod from decimal import Decimal from types import GenericAlias, UnionType -from typing import Callable, Final, Iterable, Literal, NamedTuple, OrderedDict, get_args +from typing import ( + Callable, + Final, + Generic, + Iterable, + Literal, + NamedTuple, + OrderedDict, + TypeVar, + get_args, +) from bec_lib.logger import bec_logger from bec_qthemes import material_icon @@ -350,11 +360,13 @@ class DictFormItem(DynamicFormItem): self._main_widget.replace_data(value) -class _ItemAndWidgetType(NamedTuple): - # TODO: this should be generic but not supported in 3.10 - item: type[int | float | str] +_IW = TypeVar("_IW", bound=int | float | str) + + +class _ItemAndWidgetType(NamedTuple, Generic[_IW]): + item: type[_IW] widget: type[QWidget] - default: int | float | str + default: _IW class ListFormItem(DynamicFormItem): diff --git a/docs/developer/introduction/contributing.md b/docs/developer/introduction/contributing.md index 6971c927..6c032909 100644 --- a/docs/developer/introduction/contributing.md +++ b/docs/developer/introduction/contributing.md @@ -8,7 +8,7 @@ Therefore, we recommend that you install BEC first following the [developer inst If you already have a BEC environment set up, you can install BEC Widgets in editable mode into your BEC Python environment. **Prerequisites** -1. **Python Version:** BEC Widgets requires Python version 3.10 or higher. Verify your Python version to ensure compatibility. +1. **Python Version:** BEC Widgets requires Python version 3.11 or higher. Verify your Python version to ensure compatibility. 2. **BEC Installation:** BEC Widgets works in conjunction with BEC. While BEC is a dependency and will be installed automatically, you can find more information about BEC and its installation process in the [BEC documentation](https://beamline-experiment-control.readthedocs.io/en/latest/). 3. **Qt Distributions:** BEC Widgets supports [PySide6](https://doc.qt.io/qtforpython-6/quickstart.html) and [PyQt6](https://www.riverbankcomputing.com/static/Docs/PyQt6/introduction.html). We use [qtpy](https://pypi.org/project/QtPy/) to abstract the underlying QT distribution. diff --git a/docs/user/getting_started/installation.md b/docs/user/getting_started/installation.md index 0cc7b2f4..baf1a965 100644 --- a/docs/user/getting_started/installation.md +++ b/docs/user/getting_started/installation.md @@ -4,7 +4,7 @@ Before installing BEC Widgets, please ensure the following requirements are met: -1. **Python Version:** BEC Widgets requires Python version 3.10 or higher. Verify your Python version to ensure compatibility. +1. **Python Version:** BEC Widgets requires Python version 3.11 or higher. Verify your Python version to ensure compatibility. 2. **BEC Installation:** BEC Widgets works in conjunction with BEC. While BEC is a dependency and will be installed automatically, you can find more information about BEC and its installation process in the [BEC documentation](https://beamline-experiment-control.readthedocs.io/en/latest/). **Standard Installation** diff --git a/pyproject.toml b/pyproject.toml index e9d1117e..625f14dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "bec_widgets" version = "2.38.2" description = "BEC Widgets" -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", diff --git a/tests/unit_tests/test_client_plugin_widgets.py b/tests/unit_tests/test_client_plugin_widgets.py index b43c2960..161fe2d8 100644 --- a/tests/unit_tests/test_client_plugin_widgets.py +++ b/tests/unit_tests/test_client_plugin_widgets.py @@ -28,8 +28,7 @@ def test_plugins_dont_clobber_client_globals(bec_logger: MagicMock): bec_logger.logger.warning.assert_called_with( "Plugin widget Widgets from namespace(Widgets=) conflicts with a built-in class!" ) - if sys.version_info >= (3, 11): # No EnumType in python3.10 - assert isinstance(client.Widgets, enum.EnumType) + assert isinstance(client.Widgets, enum.EnumType) class _TestDuplicatePlugin(RPCBase): ... diff --git a/tests/unit_tests/test_generated_form_items.py b/tests/unit_tests/test_generated_form_items.py index bce5d35b..abb7bec4 100644 --- a/tests/unit_tests/test_generated_form_items.py +++ b/tests/unit_tests/test_generated_form_items.py @@ -9,7 +9,6 @@ from bec_widgets.utils.forms_from_types.items import FormItemSpec, ListFormItem from bec_widgets.utils.widget_io import WidgetIO -@pytest.mark.skipif(sys.version_info < (3, 11), reason="Generic types don't support this in 3.10") @pytest.mark.parametrize( ["input", "validity"], [