diff --git a/setup.cfg b/.flake8 similarity index 100% rename from setup.cfg rename to .flake8 diff --git a/.github/actions/apt-get-qt-deps/action.yml b/.github/actions/apt-get-qt-deps/action.yml deleted file mode 100644 index 760eb7f64..000000000 --- a/.github/actions/apt-get-qt-deps/action.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This action installs dependencies for Qt on Ubuntu -# It does not install any Qt-Python wrapper. - -name: 'Install Ubuntu packages for Qt' -description: 'This action should only be run on Ubuntu' -runs: - using: "composite" - steps: - - run: | - sudo apt-get update - sudo apt-get install qtbase5-dev - sudo apt-get install qtchooser - sudo apt-get install qt5-qmake - sudo apt-get install qtbase5-dev-tools - sudo apt-get install libegl1 - sudo apt-get install libxkbcommon-x11-0 - sudo apt-get install libxcb-icccm4 - sudo apt-get install libxcb-image0 - sudo apt-get install libxcb-keysyms1 - sudo apt-get install libxcb-randr0 - sudo apt-get install libxcb-render-util0 - sudo apt-get install libxcb-xinerama0 - sudo apt-get install libxcb-shape0 - sudo apt-get install pulseaudio - sudo apt-get install libpulse-mainloop-glib0 - # Needed to work around https://bugreports.qt.io/browse/PYSIDE-1547 - sudo apt-get install libopengl0 - # Needed for Qt6 video playback - sudo apt-get install libgstreamer-gl1.0-0 - shell: bash diff --git a/.github/actions/apt-get-qt-deps/README.md b/.github/actions/install-qt-support/README.md similarity index 57% rename from .github/actions/apt-get-qt-deps/README.md rename to .github/actions/install-qt-support/README.md index 872c7221f..47c86e009 100644 --- a/.github/actions/apt-get-qt-deps/README.md +++ b/.github/actions/install-qt-support/README.md @@ -1,4 +1,4 @@ -# Install Qt dependencies on Ubuntu +# Install Qt dependencies This action calls `apt-get` to install packages required for running Qt on Ubuntu. @@ -19,11 +19,9 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - toolkit: ['pyqt5'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps - if: startsWith(matrix.os, 'ubuntu') + - uses: actions/checkout@v3 + - name: Install Qt dependencies + uses: ./.github/actions/install-qt-support ``` diff --git a/.github/actions/install-qt-support/action.yml b/.github/actions/install-qt-support/action.yml new file mode 100644 index 000000000..15e7a22a6 --- /dev/null +++ b/.github/actions/install-qt-support/action.yml @@ -0,0 +1,29 @@ +name: install-qt-support +description: 'Install supporting OS packages for Qt-using code' +runs: + using: composite + steps: + - name: Install Linux packages for Qt + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install qtbase5-dev + sudo apt-get install qtchooser + sudo apt-get install qt5-qmake + sudo apt-get install qtbase5-dev-tools + sudo apt-get install libegl1 + sudo apt-get install libxkbcommon-x11-0 + sudo apt-get install libxcb-icccm4 + sudo apt-get install libxcb-image0 + sudo apt-get install libxcb-keysyms1 + sudo apt-get install libxcb-randr0 + sudo apt-get install libxcb-render-util0 + sudo apt-get install libxcb-xinerama0 + sudo apt-get install libxcb-shape0 + sudo apt-get install pulseaudio + sudo apt-get install libpulse-mainloop-glib0 + # Needed to work around https://bugreports.qt.io/browse/PYSIDE-1547 + sudo apt-get install libopengl0 + # Needed for Qt6 video playback + sudo apt-get install libgstreamer-gl1.0-0 + shell: bash diff --git a/.github/workflows/basic-tests.yml b/.github/workflows/basic-tests.yml new file mode 100644 index 000000000..0ade97711 --- /dev/null +++ b/.github/workflows/basic-tests.yml @@ -0,0 +1,46 @@ +name: Run test suite for Qt and wxPython + +on: [pull_request, workflow_dispatch] + +env: + PYTHONUNBUFFERED: 1 + +jobs: + tests-qt: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.8', '3.11'] + qt-api: ['pyqt5', 'pyside2', 'pyside6'] + exclude: + - python-version: '3.11' + qt-api: 'pyside2' + fail-fast: false + + env: + ETS_TOOLKIT: qt + + runs-on: ${{ matrix.os }} + timeout-minutes: 20 # should be plenty, it's usually < 5 + + steps: + - name: Check out the target commit + uses: actions/checkout@v3 + - name: Install Qt dependencies + uses: ./.github/actions/install-qt-support + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and local packages + run: | + python -m pip install ".[${{ matrix.qt-api }},editors,examples,test]" + - name: Create clean test directory + run: | + mkdir testdir + - name: Run the test suite (Linux) + run: cd testdir && xvfb-run -a python -X faulthandler -m unittest discover -v traitsui + if: matrix.os == 'ubuntu-latest' + - name: Run the test suite (Windows/macOS) + run: cd testdir && python -X faulthandler -m unittest discover -v traitsui + if: matrix.os != 'ubuntu-latest' diff --git a/.github/workflows/bleeding-edge-tests.yml b/.github/workflows/bleeding-edge-tests.yml new file mode 100644 index 000000000..5f5630fc7 --- /dev/null +++ b/.github/workflows/bleeding-edge-tests.yml @@ -0,0 +1,79 @@ +name: Run test suite for Qt on bleeding-edge dependencies weekly + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + schedule: + - cron: '0 0 * * 4' + +env: + PYTHONUNBUFFERED: 1 + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.11'] + qt-api: ['pyside6'] + fail-fast: false + + env: + ETS_TOOLKIT: qt + + runs-on: ${{ matrix.os }} + timeout-minutes: 20 # should be plenty, it's usually < 5 + + steps: + - name: Check out the target commit + uses: actions/checkout@v3 + - name: Install Qt dependencies + uses: ./.github/actions/install-qt-support + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and local packages + run: | + python -m pip install ".[${{ matrix.qt-api }},editors,examples,test]" + - name: Install source dependencies + run: | + python -m pip install --force-reinstall git+http://github.com/enthought/pyface.git#egg=pyface + python -m pip install --force-reinstall git+http://github.com/enthought/traits.git#egg=traits + - name: Create clean test directory + run: | + mkdir testdir + - name: Run the test suite (Linux) + run: cd testdir && xvfb-run -a python -X faulthandler -m unittest discover -v traitsui + if: matrix.os == 'ubuntu-latest' + - name: Run the test suite (Windows/macOS) + run: cd testdir && python -X faulthandler -m unittest discover -v traitsui + if: matrix.os != 'ubuntu-latest' + + notify-on-failure: + needs: test + if: failure() + runs-on: ubuntu-latest + steps: + - name: Notify Slack channel on failure + uses: voxmedia/github-action-slack-notify-build@v1 + with: + channel_id: ${{ secrets.ETS_SLACK_CHANNEL_ID }} + status: FAILED + color: danger + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} + + notify-on-success: + needs: test + if: success() + runs-on: ubuntu-latest + steps: + - name: Notify Slack channel on success + uses: voxmedia/github-action-slack-notify-build@v1 + with: + channel_id: ${{ secrets.ETS_BOTS_SLACK_CHANNEL_ID }} + status: SUCCESS + color: good + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} diff --git a/.github/workflows/bleeding-edge.yml b/.github/workflows/bleeding-edge.yml deleted file mode 100644 index 36bcb7d1f..000000000 --- a/.github/workflows/bleeding-edge.yml +++ /dev/null @@ -1,74 +0,0 @@ -# This workflow installs dependencies from main branch - -name: ETS from source - -on: - schedule: - - cron: '0 0 * * 4' - # Make it possible to manually trigger the workflow - workflow_dispatch: - -jobs: - test: - if: github.repository == 'enthought/traitsui' - env: - # Enforce selection of toolkit - ETS_TOOLKIT: qt - QT_MAC_WANTS_LAYER: 1 - strategy: - matrix: - os: [ubuntu-20.04, macos-latest, windows-latest] - toolkit: ['pyside2'] - python-version: [3.9] - runs-on: ${{ matrix.os }} - steps: - - name: Check out - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps - if: startsWith(matrix.os, 'ubuntu') - - name: Install local packages - run: python -m pip install .[${{ matrix.toolkit }},editors,test] - - name: Install source dependencies - run: | - python -m pip install --force-reinstall git+http://github.com/enthought/pyface.git#egg=pyface - python -m pip install --force-reinstall git+http://github.com/enthought/traits.git#egg=traits - - name: Sanity check dependencies - run: python -m pip list - - name: Run test suite - uses: GabrielBB/xvfb-action@v1 - with: - run: python -m unittest discover -v traitsui - working-directory: ${{ runner.temp }} - - notify-on-failure: - needs: test - if: failure() - runs-on: ubuntu-20.04 - steps: - - name: Notify Slack channel on failure - uses: voxmedia/github-action-slack-notify-build@v1 - with: - channel_id: ${{ secrets.ETS_SLACK_CHANNEL_ID }} - status: FAILED - color: danger - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} - - notify-on-success: - needs: test - if: success() - runs-on: ubuntu-20.04 - steps: - - name: Notify Slack channel on success - uses: voxmedia/github-action-slack-notify-build@v1 - with: - channel_id: ${{ secrets.ETS_BOTS_SLACK_CHANNEL_ID }} - status: SUCCESS - color: good - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} diff --git a/.github/workflows/full-qt-tests.yml b/.github/workflows/full-qt-tests.yml new file mode 100644 index 000000000..598d02055 --- /dev/null +++ b/.github/workflows/full-qt-tests.yml @@ -0,0 +1,78 @@ +name: Run test suite for Qt across all supported versions and OS weekly + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + schedule: + - cron: '0 0 * * 4' + +env: + PYTHONUNBUFFERED: 1 + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + qt-api: ['pyqt5', 'pyside2', 'pyside6'] + exclude: + - python-version: '3.11' + qt-api: 'pyside2' + fail-fast: false + + env: + ETS_TOOLKIT: qt + + runs-on: ${{ matrix.os }} + timeout-minutes: 20 # should be plenty, it's usually < 5 + + steps: + - name: Check out the target commit + uses: actions/checkout@v3 + - name: Install Qt dependencies + uses: ./.github/actions/install-qt-support + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and local packages + run: | + python -m pip install ".[${{ matrix.qt-api }},editors,examples,test]" + - name: Create clean test directory + run: | + mkdir testdir + - name: Run the test suite (Linux) + run: cd testdir && xvfb-run -a python -X faulthandler -m unittest discover -v traitsui + if: matrix.os == 'ubuntu-latest' + - name: Run the test suite (Windows/macOS) + run: cd testdir && python -X faulthandler -m unittest discover -v traitsui + if: matrix.os != 'ubuntu-latest' + + notify-on-failure: + needs: test + if: failure() + runs-on: ubuntu-latest + steps: + - name: Notify Slack channel on failure + uses: voxmedia/github-action-slack-notify-build@v1 + with: + channel_id: ${{ secrets.ETS_SLACK_CHANNEL_ID }} + status: FAILED + color: danger + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} + + notify-on-success: + needs: test + if: success() + runs-on: ubuntu-latest + steps: + - name: Notify Slack channel on success + uses: voxmedia/github-action-slack-notify-build@v1 + with: + channel_id: ${{ secrets.ETS_BOTS_SLACK_CHANNEL_ID }} + status: SUCCESS + color: good + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} diff --git a/.github/workflows/full-wx-tests.yml b/.github/workflows/full-wx-tests.yml new file mode 100644 index 000000000..16f911fe2 --- /dev/null +++ b/.github/workflows/full-wx-tests.yml @@ -0,0 +1,37 @@ +name: Run test suite for Wx across all supported versions and OS weekly + +# This is currently failing for various reasons, so set to workflow dispatch +on: [workflow_dispatch] + +env: + PYTHONUNBUFFERED: 1 + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + fail-fast: false + + env: + ETS_TOOLKIT: wx + + runs-on: ${{ matrix.os }} + timeout-minutes: 20 # should be plenty, it's usually < 5 + + steps: + - name: Check out the target commit + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and local packages + run: | + python -m pip install ".[wx,editors,examples,test]" + - name: Create clean test directory + run: | + mkdir testdir + - name: Run the test suite (Windows/macOS) + run: cd testdir && python -X faulthandler -m unittest discover -v traitsui diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 257f689a5..27e888018 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -15,19 +15,19 @@ jobs: if: github.repository == 'enthought/traitsui' strategy: matrix: - os: [ubuntu-20.04] - toolkit: ['pyside2'] - python-version: [3.6, 3.8, 3.9] + os: [ubuntu-latest] + toolkit: ['pyside6'] + python-version: [3.8, 3.11] runs-on: ${{ matrix.os }} steps: - name: Check out - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps + uses: ./.github/actions/install-qt-support if: startsWith(matrix.os, 'ubuntu') - name: Install Swig run: sudo apt-get install swig @@ -37,16 +37,16 @@ jobs: python -m pip install --upgrade pip setuptools wheel python -m pip install .[${{ matrix.toolkit }},test,editors] python -m pip install .[examples] + - name: Create clean test directory + run: | + mkdir testdir - name: Run integration tests - uses: GabrielBB/xvfb-action@v1 - with: - run: python -m unittest discover -v ${{ github.workspace }}/integrationtests - working-directory: ${{ runner.temp }} + run: cd testdir && xvfb-run -a python -X faulthandler -m unittest discover -v ${{ github.workspace }}/integrationtests notify-on-failure: needs: test if: failure() - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Notify Slack channel on failure uses: voxmedia/github-action-slack-notify-build@v1 @@ -60,7 +60,7 @@ jobs: notify-on-success: needs: test if: success() - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Notify Slack channel on success uses: voxmedia/github-action-slack-notify-build@v1 diff --git a/.github/workflows/minimal-deps.yml b/.github/workflows/minimal-deps-tests.yml similarity index 89% rename from .github/workflows/minimal-deps.yml rename to .github/workflows/minimal-deps-tests.yml index 3efbaecaa..3846bb096 100644 --- a/.github/workflows/minimal-deps.yml +++ b/.github/workflows/minimal-deps-tests.yml @@ -14,9 +14,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Check out - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install Python packages diff --git a/.github/workflows/style-checks.yml b/.github/workflows/style-checks.yml index 079cc9cd5..9bd7aa9f4 100644 --- a/.github/workflows/style-checks.yml +++ b/.github/workflows/style-checks.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.8, 3.9] + python-version: [3.8, 3.11] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test-etsdemo.yml b/.github/workflows/test-etsdemo.yml index 82d161161..f99749158 100644 --- a/.github/workflows/test-etsdemo.yml +++ b/.github/workflows/test-etsdemo.yml @@ -9,28 +9,28 @@ on: env: QT_MAC_WANTS_LAYER: 1 - INSTALL_EDM_VERSION: 3.3.1 + INSTALL_EDM_VERSION: 3.5.0 jobs: test-etsdemo: strategy: matrix: - os: [ubuntu-20.04] - toolkit: ['pyqt5', 'pyside2'] + os: [ubuntu-latest] + toolkit: ['pyside6'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps + uses: ./.github/actions/install-qt-support if: startsWith(matrix.os, 'ubuntu') - name: Cache EDM packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache key: ${{ runner.os }}-${{ matrix.toolkit }}-${{ hashFiles('ets-demo/etstool.py') }} - name: Setup EDM - uses: enthought/setup-edm-action@v1 + uses: enthought/setup-edm-action@v2 with: edm-version: ${{ env.INSTALL_EDM_VERSION }} - name: Install click to the default EDM environment @@ -39,7 +39,5 @@ jobs: run: edm run -- python etstool.py install --toolkit=${{ matrix.toolkit }} working-directory: ets-demo - name: Run tests - uses: GabrielBB/xvfb-action@v1 - with: - run: edm run -- python etstool.py test --toolkit=${{ matrix.toolkit }} - working-directory: ets-demo + run: xvfb-run -a edm run -- python etstool.py test --toolkit=${{ matrix.toolkit }} + working-directory: ets-demo diff --git a/.github/workflows/test-with-edm.yml b/.github/workflows/test-with-edm.yml index a38b2dacc..3a9921bbd 100644 --- a/.github/workflows/test-with-edm.yml +++ b/.github/workflows/test-with-edm.yml @@ -11,7 +11,7 @@ name: Test with EDM on: [pull_request, workflow_dispatch] env: - INSTALL_EDM_VERSION: 3.3.1 + INSTALL_EDM_VERSION: 3.5.0 QT_MAC_WANTS_LAYER: 1 jobs: @@ -22,11 +22,8 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] toolkit: ['pyqt5', 'pyside2', 'pyside6'] - python-version: ['3.6', '3.8'] + python-version: ['3.8'] include: - - os: ubuntu-latest - toolkit: 'null' - python-version: '3.6' - os: ubuntu-latest toolkit: 'null' python-version: '3.8' @@ -37,8 +34,6 @@ jobs: python-version: '3.8' - toolkit: 'pyside2' python-version: '3.8' - - toolkit: 'pyside6' - python-version: '3.6' runs-on: ${{ matrix.os }} env: # Set root directory, mainly for Windows, so that the EDM Python @@ -49,24 +44,26 @@ jobs: # set the font config path for linux FONTCONFIG_PATH: /etc/fonts steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps + uses: ./.github/actions/install-qt-support if: startsWith(matrix.os, 'ubuntu') && matrix.toolkit != 'wx' && matrix.toolkit != 'null' - name: Cache EDM packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache key: ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.toolkit }}-${{ hashFiles('etstool.py') }} - name: Setup EDM - uses: enthought/setup-edm-action@v1 + uses: enthought/setup-edm-action@v2 with: edm-version: ${{ env.INSTALL_EDM_VERSION }} - name: Install click to the default EDM environment run: edm install -y wheel click coverage - name: Install test environment run: edm run -- python etstool.py install --toolkit=${{ matrix.toolkit }} --runtime=${{ matrix.python-version }} - - name: Run tests - uses: GabrielBB/xvfb-action@v1 - with: - run: edm run -- python etstool.py test --toolkit=${{ matrix.toolkit }} --runtime=${{ matrix.python-version }} + - name: Run the test suite (Linux) + run: xvfb-run -a edm run -- python etstool.py test --toolkit=${{ matrix.toolkit }} --runtime=${{ matrix.python-version }} + if: matrix.os == 'ubuntu-latest' + - name: Run the test suite (Windows/macOS) + run: edm run -- python etstool.py test --toolkit=${{ matrix.toolkit }} --runtime=${{ matrix.python-version }} + if: matrix.os != 'ubuntu-latest' diff --git a/.github/workflows/test-with-pip-wx.yml b/.github/workflows/test-with-pip-wx.yml index e79a74d71..4565aef74 100644 --- a/.github/workflows/test-with-pip-wx.yml +++ b/.github/workflows/test-with-pip-wx.yml @@ -23,13 +23,13 @@ jobs: fail-fast: false matrix: os: [windows-latest] - python-version: [3.6, 3.8] + python-version: [3.8] runs-on: ${{ matrix.os }} steps: - name: Check out - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Update pip, setuptools and wheel diff --git a/.github/workflows/test-with-pip.yml b/.github/workflows/test-with-pip.yml deleted file mode 100644 index 835153f0a..000000000 --- a/.github/workflows/test-with-pip.yml +++ /dev/null @@ -1,76 +0,0 @@ -# This workflow targets stable released dependencies from PyPI -# The matrix is conservative to avoid using too much public resources. - -# We test only Qt support in this workflow; the test suite is currently not -# maintained for wxPython. See test-with-pip-wx.yml for a separate -# wxPython-only workflow. - -name: Test with PyPI and Qt - -on: - schedule: - - cron: '0 3 * * 4' - # Make it possible to manually trigger the workflow - workflow_dispatch: - -jobs: - # Tests against Qt/Python packages from PyPI - pip-qt: - if: github.repository == 'enthought/traitsui' - env: - # Enforce selection of toolkit - ETS_TOOLKIT: qt - QT_MAC_WANTS_LAYER: 1 - strategy: - matrix: - os: [ubuntu-20.04, macos-latest, windows-latest] - toolkit: ['pyside2', 'pyside6'] - python-version: [3.6] - runs-on: ${{ matrix.os }} - steps: - - name: Check out - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install Qt dependencies for Linux - uses: ./.github/actions/apt-get-qt-deps - if: startsWith(matrix.os, 'ubuntu') - - name: Update pip, setuptools and wheel - run: python -m pip install --upgrade pip setuptools wheel - - name: Install local packages - run: python -m pip install .[${{ matrix.toolkit }},editors,test] - - name: Run test suite - uses: GabrielBB/xvfb-action@v1 - with: - run: python -m unittest discover -v traitsui - working-directory: ${{ runner.temp }} - - notify-on-failure: - needs: pip-qt - if: failure() - runs-on: ubuntu-20.04 - steps: - - name: Notify Slack channel on failure - uses: voxmedia/github-action-slack-notify-build@v1 - with: - channel_id: ${{ secrets.ETS_SLACK_CHANNEL_ID }} - status: FAILED - color: danger - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} - - notify-on-success: - needs: pip-qt - if: success() - runs-on: ubuntu-20.04 - steps: - - name: Notify Slack channel on success - uses: voxmedia/github-action-slack-notify-build@v1 - with: - channel_id: ${{ secrets.ETS_BOTS_SLACK_CHANNEL_ID }} - status: SUCCESS - color: good - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_ACTION_SECRET }} diff --git a/docs/source/traitsui_user_manual/testing/substitutions.rst b/docs/source/traitsui_user_manual/testing/substitutions.rst index a07c288e0..648cb8446 100644 --- a/docs/source/traitsui_user_manual/testing/substitutions.rst +++ b/docs/source/traitsui_user_manual/testing/substitutions.rst @@ -34,5 +34,5 @@ .. |UIWrapper.perform| replace:: :func:`~traitsui.testing.tester.ui_wrapper.UIWrapper.perform` .. |UIWrapper._target| replace:: :attr:`~traitsui.testing.tester.ui_wrapper.UIWrapper._target` -.. |ModalDialogTester| replace:: :class:`~pyface.ui.qt4.util.modal_dialog_tester.ModalDialogTester` -.. |GuiTestAssistant| replace:: :class:`~pyface.ui.qt4.util.gui_test_assistant.GuiTestAssistant` +.. |ModalDialogTester| replace:: :class:`~pyface.ui.qt.util.modal_dialog_tester.ModalDialogTester` +.. |GuiTestAssistant| replace:: :class:`~pyface.ui.qt.util.gui_test_assistant.GuiTestAssistant` diff --git a/ets-demo/etstool.py b/ets-demo/etstool.py index ef41a5eab..437bc967c 100644 --- a/ets-demo/etstool.py +++ b/ets-demo/etstool.py @@ -91,10 +91,11 @@ supported_combinations = { '3.6': {'pyside2', 'pyqt5', 'wx', 'null'}, + '3.8': {'pyside6', 'pyqt6', 'null'}, } # Default Python version to use in the comamnds below if none is specified. -DEFAULT_RUNTIME = '3.6' +DEFAULT_RUNTIME = '3.8' # Default toolkit to use if none specified. DEFAULT_TOOLKIT = 'null' @@ -122,6 +123,10 @@ 'pyside2': { "pygments", }, + 'pyside6': { + 'pyside6', + 'pygments', + }, 'pyqt5': { 'pyqt5', 'pygments', @@ -157,6 +162,7 @@ environment_vars = { 'pyside2': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyside2'}, + 'pyside6': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyside6'}, 'pyqt5': {"ETS_TOOLKIT": "qt4", "QT_API": "pyqt5"}, 'wx': {'ETS_TOOLKIT': 'wx'}, 'null': {'ETS_TOOLKIT': 'null'}, @@ -204,19 +210,17 @@ def install(runtime, toolkit, environment, editable): install_here += "." # edm commands to setup the development environment - if sys.platform == 'linux': - commands = [ - "edm environments create {environment} --platform=rh6-x86_64 --force --version={runtime}" - ] # noqa: E501 - else: - commands = [ - "edm environments create {environment} --force --version={runtime}" - ] # noqa: E501 + commands = [ + "edm environments create {environment} --force --version={runtime}" + ] # noqa: E501 commands.extend( [ - "edm install -y -e {environment} " + packages, - "edm run -e {environment} -- python setup.py clean --all", + ( + "edm install -y -e {environment} " + "--add-repository enthought/lgpl --add-repository enthought/gpl " + ) + + packages, install_here, ] ) @@ -245,7 +249,6 @@ def install(runtime, toolkit, environment, editable): def shell(runtime, toolkit, environment): """Create a shell into the EDM development environment (aka 'activate' it). - """ parameters = get_parameters(runtime, toolkit, environment) commands = [ diff --git a/etstool.py b/etstool.py index 836783504..5537c0174 100644 --- a/etstool.py +++ b/etstool.py @@ -108,7 +108,6 @@ )) supported_combinations = { - '3.6': {'pyside2', 'pyside6', 'pyqt5', 'pyqt6', 'wx', 'null'}, '3.8': {'pyside2', 'pyside6', 'pyqt5', 'pyqt6', 'wx', 'null'}, } @@ -118,11 +117,12 @@ # Default toolkit to use if none specified. DEFAULT_TOOLKIT = 'null' -# Required runtime dependencies. Should match install_requires in setup.py +# Required runtime dependencies. Should match pyproject.toml. dependencies = { # temporarily get pyface from pip until EDM release # "pyface>=7.4.1", "traits", + "setuptools", } # Dependencies we install from source for cron tests @@ -133,7 +133,7 @@ "traits": "git+http://github.com/enthought/traits.git#egg=traits", } -# The following should match extras_require in setup.py but with package +# The following should match extras_require in pyproject.toml but with package # names compatible with EDM extra_dependencies = { 'pyside2': { @@ -174,7 +174,6 @@ # Extra runtime dependencies runtime_dependencies = { - "3.6": {"pillow", "pytables"}, "3.8": {"pillow_simd", "tables"}, } @@ -209,8 +208,6 @@ # toolkit versions in EDM edm_versions = { - ('3.6', 'pyside2'), - ('3.6', 'pyqt5'), ('3.8', 'pyside6'), ('3.8', 'pyqt6'), } @@ -284,23 +281,13 @@ def install(runtime, toolkit, environment, editable, source): "edm -c {config} run -e {environment} -- pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/ wxPython" ) elif toolkit == 'pyside6': - # On Linux and macOS, some versions of PySide6 between 6.2.2 and 6.3.0 - # are unimportable on Python 3.6 and Python 3.7. See - # https://bugreports.qt.io/browse/PYSIDE-1797. It may be possible to - # remove this workaround once PySide6 6.3.1 or later is released. - # Also there are currently issues with PySide 6.4 - if sys.platform in {'darwin', 'linux'}: - commands.append( - "edm -c {config} run -e {environment} -- pip install pyside6<6.2.2") - else: - commands.append('edm run -e {environment} -- pip install "pyside6<6.4.0"') + commands.append('edm run -e {environment} -- pip install "pyside6"') elif toolkit != "null": commands.append("edm -c {config} run -e {environment} -- pip install {toolkit}") commands.extend( [ "edm -c {config} run -e {environment} -- pip install --force-reinstall -r ci-src-requirements.txt --no-dependencies", - "edm -c {config} run -e {environment} -- python setup.py clean --all", ] ) @@ -395,7 +382,6 @@ def cleanup(runtime, toolkit, environment): """Remove a development environment.""" parameters = get_parameters(runtime, toolkit, environment) commands = [ - "edm -c {config} run -e {environment} -- python setup.py clean", "edm -c {config} environments remove {environment} --purge -y", ] click.echo("Cleaning up environment '{environment}'".format(**parameters)) @@ -416,19 +402,6 @@ def test_clean(runtime, toolkit): cleanup(args=args, standalone_mode=False) -@cli.command() -@click.option('--runtime', default=DEFAULT_RUNTIME) -@click.option('--toolkit', default=DEFAULT_TOOLKIT) -@click.option('--environment', default=None) -def update(runtime, toolkit, environment): - """Update/Reinstall package into environment.""" - parameters = get_parameters(runtime, toolkit, environment) - commands = ["edm -c {config} run -e {environment} -- python setup.py install"] - click.echo("Re-installing in '{environment}'".format(**parameters)) - execute(commands, parameters) - click.echo('Done update') - - @cli.command() @click.option('--runtime', default=DEFAULT_RUNTIME) @click.option('--toolkit', default=DEFAULT_TOOLKIT) diff --git a/pyproject.toml b/pyproject.toml index f658121cd..a3cdb66a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,75 @@ +[project] +name = 'traitsui' +description = 'Traits-capable user interfaces' +readme = 'README.rst' +requires-python = '>=3.7' +authors = [{name='Enthought', email='info@enthought.com'}] +keywords = ['gui', 'traits', 'traitsui', 'pyqt', 'pyside', 'wxpython'] +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', +] +dependencies = [ + 'traits>=6.2', + 'pyface>=7.4.1', + 'importlib-metadata>=3.6; python_version<"3.8"', +] +license = {file = "LICENSE.txt"} +version = "8.0.0.dev0" + +[project.entry-points.'traitsui.toolkits'] +qt = 'traitsui.qt4:toolkit' +qt4 = 'traitsui.qt4:toolkit' +wx = 'traitsui.wx:toolkit' +null = 'traitsui.null:toolkit' + +[project.entry-points.'etsdemo_data'] +demo = 'traitsui.extras._demo_info:info' + +[project.optional-dependencies] +docs = ['enthought-sphinx-theme', 'sphinx', 'sphinx-copybutton'] +editors = ['numpy', 'pandas'] +examples = ['apptools', 'h5py', 'numpy', 'pandas', 'pillow', 'tables'] +pyqt5 = ['pyqt5', 'pygments'] +pyqt6 = ['pyqt6', 'pygments'] +pyside2 = ['pyside2', 'pygments'] +pyside6 = ['pyside6', 'pygments'] +test = ['packaging', 'numpy'] +wx = ['wxPython>=4', 'numpy'] + +[project.urls] +source = 'https://github.com/enthought/traitsui' +docs = 'https://docs.enthought.com/traitsui' + +[build-system] +requires = ['setuptools>=61', 'wheel'] +build-backend = 'setuptools.build_meta' + [tool.black] line-length = 79 -target-version = ['py36', 'py37', 'py38'] -skip-string-normalization = true + +[tool.isort] +profile = 'black' +sections = ['FUTURE', 'STDLIB', 'THIRDPARTY', 'ENTHOUGHT', 'FIRSTPARTY', 'LOCALFOLDER'] +known_enthought = ['apptools', 'pyface', 'traits'] +line_length = 79 +order_by_type = false + +[tool.setuptools] +packages = ['traitsui'] + +[tool.setuptools.package-data] +traitsui = [ + 'examples/demo/*', + 'examples/demo/*/*', + 'examples/demo/*/*/*', + 'extras/images/*', + 'image/library/*.zip', + 'images/*', + 'wx/images/*', + 'qt4/images/*', + 'testing/data/*', +] diff --git a/setup.py b/setup.py deleted file mode 100644 index fabb9860a..000000000 --- a/setup.py +++ /dev/null @@ -1,375 +0,0 @@ -# (C) Copyright 2004-2023 Enthought, Inc., Austin, TX -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only under -# the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! - -import os -import re -import runpy -import subprocess - -from setuptools import setup, find_packages -from io import open - -# Version information; update this by hand when making a new bugfix or feature -# release. The actual package version is autogenerated from this information -# together with information from the version control system, and then injected -# into the package source. -MAJOR = 7 -MINOR = 5 -MICRO = 0 -PRERELEASE = "" -IS_RELEASED = False - -# If this file is part of a Git export (for example created with "git archive", -# or downloaded from GitHub), ARCHIVE_COMMIT_HASH gives the full hash of the -# commit that was exported. -ARCHIVE_COMMIT_HASH = "$Format:%H$" - -# Templates for version strings. -RELEASED_VERSION = "{major}.{minor}.{micro}{prerelease}" -UNRELEASED_VERSION = "{major}.{minor}.{micro}{prerelease}.dev{dev}" - -# Paths to the autogenerated version file and the Git directory. -HERE = os.path.abspath(os.path.dirname(__file__)) -VERSION_FILE = os.path.join(HERE, "traitsui", "_version.py") -GIT_DIRECTORY = os.path.join(HERE, ".git") - -# Template for the autogenerated version file. -VERSION_FILE_TEMPLATE = """\ -# (C) Copyright 2004-2023 Enthought, Inc., Austin, TX -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only under -# the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! - -# THIS FILE IS GENERATED FROM SETUP.PY - -#: The full version of the package, including a development suffix -#: for unreleased versions of the package. -version = '{version}' - -#: The full version of the package, same as 'version' -#: Kept for backward compatibility -full_version = version - -#: The Git revision from which this release was made. -git_revision = '{git_revision}' - -#: Flag whether this is a final release -is_released = {is_released} -""" - - -def read_module(module, package='traitsui'): - """Read a simple .py file from traitsui in a safe way. - - It would be simpler to import the file, but that can be problematic in an - unknown system, so we exec the file instead and extract the variables. - - This will fail if things get too complex in the file being read, but is - sufficient to get version and requirements information. - """ - base_dir = os.path.dirname(__file__) - module_name = package + '.' + module - path = os.path.join(base_dir, package, module + '.py') - with open(path, 'r', encoding='utf-8') as fp: - code = compile(fp.read(), module_name, 'exec') - context = {} - exec(code, context) - return context - - -# Return the git revision as a string -def _git_info(): - """ - Get information about the given commit from Git. - - Returns - ------- - git_count : int - Number of revisions from this commit to the initial commit. - git_revision : str - Commit hash for HEAD. - - Raises - ------ - EnvironmentError - If Git is not available. - subprocess.CalledProcessError - If Git is available, but the version command fails (most likely - because there's no Git repository here). - """ - - def _minimal_ext_cmd(cmd): - # construct minimal environment - env = {} - for k in ['SYSTEMROOT', 'PATH']: - v = os.environ.get(k) - if v is not None: - env[k] = v - # LANGUAGE is used on win32 - env['LANGUAGE'] = 'C' - env['LANG'] = 'C' - env['LC_ALL'] = 'C' - out = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - env=env, - ).communicate()[0] - return out - - try: - out = _minimal_ext_cmd(['git', 'describe', '--tags']) - except OSError: - out = '' - - git_description = out.strip().decode('ascii') - expr = r'.*?\-(?P\d+)-g(?P[a-fA-F0-9]+)' - match = re.match(expr, git_description) - if match is None: - git_revision, git_count = 'Unknown', '0' - else: - git_revision, git_count = match.group('hash'), match.group('count') - - return git_count, git_revision - - -def git_version(): - """ - Construct version information from local variables and Git. - - Returns - ------- - version : str - Package version. - git_revision : str - The full commit hash for the current Git revision. - - Raises - ------ - EnvironmentError - If Git is not available. - subprocess.CalledProcessError - If Git is available, but the version command fails (most likely - because there's no Git repository here). - """ - git_count, git_revision = _git_info() - version_template = RELEASED_VERSION if IS_RELEASED else UNRELEASED_VERSION - version = version_template.format( - major=MAJOR, - minor=MINOR, - micro=MICRO, - prerelease=PRERELEASE, - dev=git_count, - ) - return version, git_revision - - -def archive_version(): - """ - Construct version information for an archive. - - Returns - ------- - version : str - Package version. - git_revision : str - The full commit hash for the current Git revision. - - Raises - ------ - ValueError - If this does not appear to be an archive. - """ - if "$" in ARCHIVE_COMMIT_HASH: - raise ValueError("This does not appear to be an archive.") - - version_template = RELEASED_VERSION if IS_RELEASED else UNRELEASED_VERSION - version = version_template.format( - major=MAJOR, - minor=MINOR, - micro=MICRO, - prerelease=PRERELEASE, - dev="-unknown", - ) - return version, ARCHIVE_COMMIT_HASH - - -def write_version_file(version, git_revision, filename=VERSION_FILE): - """ - Write version information to the version file. - - Overwrites any existing version file. - - Parameters - ---------- - version : str - Package version. - git_revision : str - The full commit hash for the current Git revision. - filename : str - Path to the version file. - """ - with open(filename, "w", encoding="utf-8") as version_file: - version_file.write( - VERSION_FILE_TEMPLATE.format( - version=version, - git_revision=git_revision, - is_released=IS_RELEASED, - ) - ) - - -def read_version_file(): - """ - Read version information from the version file, if it exists. - - Returns - ------- - version : str - The full version, including any development suffix. - git_revision : str - The full commit hash for the current Git revision. - - Raises - ------ - EnvironmentError - If the version file does not exist. - """ - version_info = runpy.run_path(VERSION_FILE) - return (version_info["version"], version_info["git_revision"]) - - -def resolve_version(): - """ - Process version information and write a version file if necessary. - - Returns the current version information. - - Returns - ------- - version : str - Package version. - git_revision : str - The full commit hash for the current Git revision. - """ - if os.path.isdir(GIT_DIRECTORY): - # This is a local clone; compute version information and write - # it to the version file, overwriting any existing information. - version = git_version() - print("Computed package version: {}".format(version)) - print("Writing version to version file {}.".format(VERSION_FILE)) - write_version_file(*version) - elif "$" not in ARCHIVE_COMMIT_HASH: - # This is a source archive. - version = archive_version() - print("Archive package version: {}".format(version)) - print("Writing version to version file {}.".format(VERSION_FILE)) - write_version_file(*version) - elif os.path.isfile(VERSION_FILE): - # This is a source distribution. Read the version information. - print("Reading version file {}".format(VERSION_FILE)) - version = read_version_file() - print("Package version from version file: {}".format(version)) - else: - raise RuntimeError( - "Unable to determine package version. No local Git clone " - "detected, and no version file found at {}.".format(VERSION_FILE) - ) - - return version - - -if __name__ == "__main__": - __version__, _ = resolve_version() - data = read_module('__init__') - __requires__ = data['__requires__'] - __extras_require__ = data['__extras_require__'] - with open("README.rst", "r", encoding="utf-8") as readme: - LONG_DESCRIPTION = readme.read().split(".. end_of_long_description")[0] - - def additional_commands(): - try: - from sphinx.setup_command import BuildDoc - except ImportError: - return {} - else: - return {'documentation': BuildDoc} - - setup( - name='traitsui', - version=__version__, - author='David C. Morrill, et. al.', - author_email='dmorrill@enthought.com', - classifiers=[ - c.strip() - for c in """\ - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Operating System :: MacOS - Operating System :: Microsoft :: Windows - Operating System :: OS Independent - Operating System :: POSIX - Operating System :: Unix - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Topic :: Scientific/Engineering - Topic :: Software Development - Topic :: Software Development :: Libraries - """.splitlines() - if len(c.strip()) > 0 - ], - description='traitsui: traits-capable user interfaces', - long_description=LONG_DESCRIPTION, - long_description_content_type="text/x-rst", - url='http://docs.enthought.com/traitsui', - download_url='https://github.com/enthought/traitsui', - install_requires=__requires__, - extras_require=__extras_require__, - license='BSD', - maintainer='ETS Developers', - maintainer_email='enthought-dev@enthought.com', - package_data=dict( - traitsui=[ - 'examples/demo/*', - 'examples/demo/*/*', - 'examples/demo/*/*/*', - 'extras/images/*', - 'image/library/*.zip', - 'images/*', - 'wx/images/*', - 'qt4/images/*', - 'testing/data/*', - ] - ), - packages=find_packages(), - entry_points={ - 'traitsui.toolkits': [ - 'qt4 = traitsui.qt4:toolkit', - 'wx = traitsui.wx:toolkit', - 'qt = traitsui.qt4:toolkit', - 'null = traitsui.null:toolkit', - ], - 'etsdemo_data': [ - 'demo = traitsui.extras._demo_info:info', - ], - }, - platforms=["Windows", "Linux", "Mac OS-X", "Unix", "Solaris"], - python_requires=">=3.6", - zip_safe=False, - ) diff --git a/traitsui/__init__.py b/traitsui/__init__.py index 6d5668a19..4519d6a63 100644 --- a/traitsui/__init__.py +++ b/traitsui/__init__.py @@ -8,57 +8,26 @@ # # Thanks for using Enthought open source! +def __getattr__(name): + """Handle deprecated attributes.""" + if name == "__version__": + try: + from importlib.metadata import version + except ImportError: + from importlib_metadata import version + from warnings import warn -try: - from traitsui._version import full_version as __version__ -except ImportError: - __version__ = "not-built" - -__requires__ = ["traits>=6.2.0", "pyface>=7.4.1"] -__extras_require__ = { - "wx": ["wxpython>=4", "numpy"], - "pyqt": ["pyqt>=4.10", "pygments"], - "pyqt5": ["pyqt5", "pygments"], - "pyside2": ["pyside2", "pygments"], - "pyside6": [ - # Avoid https://bugreports.qt.io/browse/PYSIDE-1797, which causes - # some versions of PySide6 to be unimportable on Python 3.6 and 3.7. - # Also avoid PySide 6.4 until enum issue is resolved. - "pyside6!=6.2.2,!=6.2.2.1,!=6.2.3,!=6.2.4,!=6.3.0,<6.4.0; python_version<'3.8'", - "pyside6<6.4.0; python_version>='3.8'", - "pygments", - ], - "docs": ["enthought-sphinx-theme", "sphinx", "sphinx-copybutton"], - "demo": [ - # to be deprecated, see enthought/traitsui#950 - "configobj", - "docutils", - ], - "examples": [ - # Dependencies for examples - "apptools", - "h5py", - "numpy", - "pandas", - "pillow", - "tables", - ], - "editors": [ - # Optional dependencies for certain editors which may not be needed by - # projects. If they are absent, ``traitsui.api``` should still be - # importable and the relevant tests should be skipped. - "numpy", # For ArrayEditor and DataFrameEditor - "pandas", # For DataFrameEditor - ], - "test": [ - # Dependencies for running test suites. - "packaging", - "numpy", - ], -} + warn( + f"traitsui.{name} is deprecated, " + f"use impportlib.metadata.version('traitsui') ", + DeprecationWarning, + ) + return version('traitsui') + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") # ============================= Test Loader ================================== + def load_tests(loader, standard_tests, pattern): """Custom test loading function that enables test filtering using regex exclusion pattern. diff --git a/traitsui/editors/shell_editor.py b/traitsui/editors/shell_editor.py index 906c5d575..92222be04 100644 --- a/traitsui/editors/shell_editor.py +++ b/traitsui/editors/shell_editor.py @@ -50,6 +50,7 @@ def init(self, parent): if self.factory.share and isinstance(value, dict): locals = value self._shell = shell = PythonShell(parent) + shell.create() self.control = shell.control if locals: for item in locals.items(): diff --git a/traitsui/qt4/clipboard.py b/traitsui/qt4/clipboard.py index f07d670a1..fad4f7635 100644 --- a/traitsui/qt4/clipboard.py +++ b/traitsui/qt4/clipboard.py @@ -25,7 +25,10 @@ """ from pyface.qt import QtGui -from pyface.ui.qt4.mimedata import PyMimeData, str2bytes +try: + from pyface.ui.qt.mimedata import PyMimeData, str2bytes +except ModuleNotFoundError: + from pyface.ui.qt4.mimedata import PyMimeData, str2bytes from traits.api import HasTraits, Instance, Property diff --git a/traitsui/qt4/code_editor.py b/traitsui/qt4/code_editor.py index ed1eb564c..c835801c7 100644 --- a/traitsui/qt4/code_editor.py +++ b/traitsui/qt4/code_editor.py @@ -25,9 +25,12 @@ """ -from pyface.qt import QtCore, QtGui - -from pyface.ui.qt4.code_editor.code_widget import AdvancedCodeWidget +from pyface.qt import QtGui +from pyface.key_pressed_event import KeyPressedEvent +try: + from pyface.ui.qt.code_editor.code_widget import AdvancedCodeWidget +except ModuleNotFoundError: + from pyface.ui.qt4.code_editor.code_widget import AdvancedCodeWidget from traits.api import ( Str, List, @@ -39,8 +42,6 @@ ) from traits.trait_base import SequenceTypes -from pyface.key_pressed_event import KeyPressedEvent - from .constants import OKColor, ErrorColor from .editor import Editor from .helper import pixmap_cache diff --git a/traitsui/qt4/progress_editor.py b/traitsui/qt4/progress_editor.py index 803945592..2ccaaf9a7 100644 --- a/traitsui/qt4/progress_editor.py +++ b/traitsui/qt4/progress_editor.py @@ -13,8 +13,11 @@ from pyface.qt import QtGui, QtCore from traits.api import Instance, Int, Str +try: + from pyface.ui.qt.progress_dialog import ProgressDialog +except ModuleNotFoundError: + from pyface.ui.qt4.progress_dialog import ProgressDialog from traitsui.qt4.editor import Editor -from pyface.ui.qt4.progress_dialog import ProgressDialog class _ProgressDialog(ProgressDialog): diff --git a/traitsui/qt4/tabular_editor.py b/traitsui/qt4/tabular_editor.py index 7905c3ea3..17adab7d0 100644 --- a/traitsui/qt4/tabular_editor.py +++ b/traitsui/qt4/tabular_editor.py @@ -40,10 +40,10 @@ from .tabular_model import TabularModel SCROLL_TO_POSITION_HINT_MAP = { - "center": QtGui.QTableView.PositionAtCenter, - "top": QtGui.QTableView.PositionAtTop, - "bottom": QtGui.QTableView.PositionAtBottom, - "visible": QtGui.QTableView.EnsureVisible, + "center": QtGui.QTableView.ScrollHint.PositionAtCenter, + "top": QtGui.QTableView.ScrollHint.PositionAtTop, + "bottom": QtGui.QTableView.ScrollHint.PositionAtBottom, + "visible": QtGui.QTableView.ScrollHint.EnsureVisible, } @@ -506,7 +506,8 @@ def _selected_column_changed(self, selected_column): def _scroll_to_row_changed(self, row): """Scroll to the given row.""" scroll_hint = SCROLL_TO_POSITION_HINT_MAP.get( - self.factory.scroll_to_position_hint, self.control.EnsureVisible + self.factory.scroll_to_position_hint, + self.control.ScrollHint.EnsureVisible ) self.control.scrollTo( self.model.index(row, max(self.selected_column, 0)), scroll_hint @@ -515,7 +516,8 @@ def _scroll_to_row_changed(self, row): def _scroll_to_column_changed(self, column): """Scroll to the given column.""" scroll_hint = SCROLL_TO_POSITION_HINT_MAP.get( - self.factory.scroll_to_position_hint, self.control.EnsureVisible + self.factory.scroll_to_position_hint, + self.control.ScrollHint.EnsureVisible ) self.control.scrollTo( self.model.index(max(self.selected_row, 0), column), scroll_hint diff --git a/traitsui/qt4/tests/test_ui_base.py b/traitsui/qt4/tests/test_ui_base.py index 8482aaa00..72309f3c0 100644 --- a/traitsui/qt4/tests/test_ui_base.py +++ b/traitsui/qt4/tests/test_ui_base.py @@ -37,5 +37,6 @@ def test_sticky_dialog_with_parent(self): from pyface.qt import QtCore self.assertFalse( - ui2.control.windowFlags() & QtCore.Qt.WindowState.WindowMaximized + ui2.control.windowState() + & QtCore.Qt.WindowState.WindowMaximized ) diff --git a/traitsui/testing/tests/test_exception_handling.py b/traitsui/testing/tests/test_exception_handling.py index 28018c201..f47b01d1b 100644 --- a/traitsui/testing/tests/test_exception_handling.py +++ b/traitsui/testing/tests/test_exception_handling.py @@ -42,7 +42,8 @@ def raise_error_2(): with reraise_exceptions(): gui.invoke_later(raise_error_1) gui.invoke_later(raise_error_2) - gui.process_events() + gui.invoke_after(100, gui.stop_event_loop) + gui.start_event_loop() error_msg = str(exception_context.exception) self.assertIn("ZeroDivisionError", error_msg) diff --git a/traitsui/tests/editors/test_code_editor.py b/traitsui/tests/editors/test_code_editor.py index 3b943b2c0..be599955b 100644 --- a/traitsui/tests/editors/test_code_editor.py +++ b/traitsui/tests/editors/test_code_editor.py @@ -48,6 +48,7 @@ def default_traits_view(self): class TestCodeEditor(BaseTestMixin, unittest.TestCase): + def setUp(self): BaseTestMixin.setUp(self) diff --git a/traitsui/tests/editors/test_date_editor.py b/traitsui/tests/editors/test_date_editor.py index 372e9c1ea..37f3ee208 100644 --- a/traitsui/tests/editors/test_date_editor.py +++ b/traitsui/tests/editors/test_date_editor.py @@ -151,7 +151,7 @@ def check_select_status(self, editor, date, selected): ) self.assertEqual( textformat.background().style(), - 0, # Qt.BrushStyle.NoBrush, + QtCore.Qt.BrushStyle.NoBrush, "Expected brush to have been reset.", ) self.check_date_bgcolor(editor, date, (0, 0, 0)) diff --git a/traitsui/tests/editors/test_date_range_editor.py b/traitsui/tests/editors/test_date_range_editor.py index b4f662c6d..150945b1c 100644 --- a/traitsui/tests/editors/test_date_range_editor.py +++ b/traitsui/tests/editors/test_date_range_editor.py @@ -255,7 +255,7 @@ def check_select_status(self, editor, date, selected): ) self.assertEqual( textformat.background().style(), - 0, # Qt.BrushStyle.NoBrush, + QtCore.Qt.BrushStyle.NoBrush, "Expected brush to have been reset.", ) self.assertEqual( diff --git a/traitsui/tests/editors/test_liststr_editor_selection.py b/traitsui/tests/editors/test_liststr_editor_selection.py index 2a19757d9..e8472824f 100644 --- a/traitsui/tests/editors/test_liststr_editor_selection.py +++ b/traitsui/tests/editors/test_liststr_editor_selection.py @@ -744,7 +744,10 @@ def test_wx_list_str_multi_selected_index(self): def test_selection_listener_disconnected(self): """Check that selection listeners get correctly disconnected""" from pyface.qt.QtGui import QApplication, QItemSelectionModel - from pyface.ui.qt4.util.testing import event_loop + try: + from pyface.ui.qt.util.testing import event_loop + except ModuleNotFoundError: + from pyface.ui.qt4.util.testing import event_loop obj = ListStrEditorWithSelectedIndex(values=["value1", "value2"]) diff --git a/traitsui/tests/editors/test_shell_editor.py b/traitsui/tests/editors/test_shell_editor.py index 0b2f25202..ab7c430a8 100644 --- a/traitsui/tests/editors/test_shell_editor.py +++ b/traitsui/tests/editors/test_shell_editor.py @@ -45,6 +45,7 @@ def get_dict_view(share=False): @requires_toolkit([ToolkitName.qt, ToolkitName.wx]) class TestShellEditor(BaseTestMixin, unittest.TestCase): + def setUp(self): BaseTestMixin.setUp(self) diff --git a/traitsui/tests/editors/test_video_editor.py b/traitsui/tests/editors/test_video_editor.py index 6be823cd3..24a775be3 100644 --- a/traitsui/tests/editors/test_video_editor.py +++ b/traitsui/tests/editors/test_video_editor.py @@ -7,7 +7,11 @@ # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! + +import os import unittest +import subprocess +import sys try: import numpy as np # noqa: F401 @@ -23,6 +27,18 @@ filename = pkg_resources.resource_filename('traitsui.testing', 'data/test.mp4') +# Is a MacOS machine lacking the standard Metal APIs? +metal_api_missing = False +if sys.platform == 'darwin' and is_qt6: + # TODO: would be nice to detect if Qt build _uses_ Metal + result = subprocess.run( + ["system_profiler", "SPDisplaysDataType"], + capture_output=True, + check=True, + ) + metal_api_missing = (b'Metal Family: Supported' not in result.stdout) + + class MovieTheater(HasTraits): url = File(filename) @@ -39,6 +55,7 @@ class MovieTheater(HasTraits): @unittest.skipIf(not is_qt5() and not is_qt6(), 'Requires Qt5 or 6') +@unittest.skipIf(metal_api_missing, "Mac Qt6 video editor requires Metal API") class TestVideoEditor(BaseTestMixin, unittest.TestCase): def setUp(self): BaseTestMixin.setUp(self) diff --git a/traitsui/tests/test_actions.py b/traitsui/tests/test_actions.py index 49e88939f..54462028d 100644 --- a/traitsui/tests/test_actions.py +++ b/traitsui/tests/test_actions.py @@ -149,10 +149,12 @@ def test_qt_toolbar_action(self): # Bug: in the Qt4 backend, a # TypeError: perform() takes exactly 2 arguments (1 given) was raised # instead + try: + from pyface.ui.qt.action.tool_bar_manager import _ToolBar + except ModuleNotFoundError: + from pyface.ui.qt4.action.tool_bar_manager import _ToolBar - qt_trigger_toolbar_action = partial( - _qt_trigger_action, pyface.ui.qt4.action.tool_bar_manager._ToolBar - ) + qt_trigger_toolbar_action = partial(_qt_trigger_action, _ToolBar) self._test_actions(qt_trigger_toolbar_action) @@ -164,9 +166,13 @@ def test_qt_menu_action(self): # Bug: in the Qt4 backend, a # TypeError: perform() takes exactly 2 arguments (1 given) was raised # instead + try: + from pyface.ui.qt.action.menu_manager import _Menu + except ModuleNotFoundError: + from pyface.ui.qt4.action.menu_manager import _Menu qt_trigger_menu_action = partial( - _qt_trigger_action, pyface.ui.qt4.action.menu_manager._Menu + _qt_trigger_action, _Menu ) self._test_actions(qt_trigger_menu_action) @@ -270,10 +276,12 @@ def test_qt_toolbar_action(self): # Bug: in the Qt4 backend, a # TypeError: perform() takes exactly 2 arguments (1 given) was raised # instead + try: + from pyface.ui.qt.action.tool_bar_manager import _ToolBar + except ModuleNotFoundError: + from pyface.ui.qt4.action.tool_bar_manager import _ToolBar - qt_trigger_toolbar_action = partial( - _qt_trigger_action, pyface.ui.qt4.action.tool_bar_manager._ToolBar - ) + qt_trigger_toolbar_action = partial(_qt_trigger_action, _ToolBar) self._test_actions(qt_trigger_toolbar_action) @@ -285,10 +293,12 @@ def test_qt_menu_action(self): # Bug: in the Qt4 backend, a # TypeError: perform() takes exactly 2 arguments (1 given) was raised # instead + try: + from pyface.ui.qt.action.menu_manager import _Menu + except ModuleNotFoundError: + from pyface.ui.qt4.action.menu_manager import _Menu - qt_trigger_menu_action = partial( - _qt_trigger_action, pyface.ui.qt4.action.menu_manager._Menu - ) + qt_trigger_menu_action = partial(_qt_trigger_action, _Menu) self._test_actions(qt_trigger_menu_action) diff --git a/traitsui/tests/test_view_application.py b/traitsui/tests/test_view_application.py index 4317651a3..fd2a21689 100644 --- a/traitsui/tests/test_view_application.py +++ b/traitsui/tests/test_view_application.py @@ -102,7 +102,10 @@ def close_dialog(self): def click_button(self, text): if is_qt(): from pyface.qt.QtGui import QPushButton - from pyface.ui.qt4.util.testing import find_qt_widget + try: + from pyface.ui.qt.util.testing import find_qt_widget + except ModuleNotFoundError: + from pyface.ui.qt4.util.testing import find_qt_widget button = find_qt_widget( self.handler.info.ui.control, diff --git a/traitsui/wx/code_editor.py b/traitsui/wx/code_editor.py index 4446f271c..7fc8d00be 100644 --- a/traitsui/wx/code_editor.py +++ b/traitsui/wx/code_editor.py @@ -96,6 +96,7 @@ def init(self, parent): self._editor = editor = PythonEditor( parent, show_line_numbers=factory.show_line_numbers ) + editor.create() self.control = control = editor.control # There are a number of events which aren't well documented that look diff --git a/traitsui/wx/file_editor.py b/traitsui/wx/file_editor.py index e387e3e98..396a08b34 100644 --- a/traitsui/wx/file_editor.py +++ b/traitsui/wx/file_editor.py @@ -139,7 +139,7 @@ def update_editor(self): else: self._file_name.SetValue(self.str_value) - def show_file_dialog(self, event): + def show_file_dialog(self, event=None): """Displays the pop-up file dialog.""" if self.history is not None: self.popup = self._create_file_popup() diff --git a/traitsui/wx/font_trait.py b/traitsui/wx/font_trait.py index 2a424210a..db98825db 100644 --- a/traitsui/wx/font_trait.py +++ b/traitsui/wx/font_trait.py @@ -49,12 +49,12 @@ def font_to_str(font): """Converts a wx.Font into a string description of itself.""" family = { - wx.FONTFAMILY_DECORATIVE: "decorative family", - wx.FONTFAMILY_ROMAN: "roman family", - wx.FONTFAMILY_SCRIPT: "script family", - wx.FONTFAMILY_SWISS: "swiss family", - wx.FONTFAMILY_MODERN: "modern family", - wx.FONTFAMILY_TELETYPE: "typewriter family", + wx.FONTFAMILY_DECORATIVE: "decorative ", + wx.FONTFAMILY_ROMAN: "roman ", + wx.FONTFAMILY_SCRIPT: "script ", + wx.FONTFAMILY_SWISS: "swiss ", + wx.FONTFAMILY_MODERN: "modern ", + wx.FONTFAMILY_TELETYPE: "typewriter ", }.get(font.GetFamily(), "") weight = {wx.FONTWEIGHT_LIGHT: " Light", wx.FONTWEIGHT_BOLD: " Bold"}.get( font.GetWeight(), "" diff --git a/traitsui/wx/list_str_editor.py b/traitsui/wx/list_str_editor.py index 911996c96..540fa48c6 100644 --- a/traitsui/wx/list_str_editor.py +++ b/traitsui/wx/list_str_editor.py @@ -317,7 +317,7 @@ def update_editor(self): if index is None: visible = top + pn - 2 if visible >= 0 and visible < control.GetItemCount(): - control.EnsureVisible(visible) + control.ScrollHint.EnsureVisible(visible) if self.factory.multi_select: for index in self.multi_selected_indices: if 0 <= index < n: @@ -336,15 +336,15 @@ def update_editor(self): return if 0 <= (index - top) < pn: - control.EnsureVisible( + control.ScrollHint.EnsureVisible( min(top + pn - 2, control.GetItemCount() - 1) ) elif index < top: - control.EnsureVisible( + control.ScrollHint.EnsureVisible( min(index + pn - 1, control.GetItemCount() - 1) ) else: - control.EnsureVisible(index) + control.ScrollHint.EnsureVisible(index) control.SetItemState( index, diff --git a/traitsui/wx/tabular_editor.py b/traitsui/wx/tabular_editor.py index d148bf200..d0f7d7cd3 100644 --- a/traitsui/wx/tabular_editor.py +++ b/traitsui/wx/tabular_editor.py @@ -494,19 +494,19 @@ def update_editor(self): if row is None: visible = bottom if visible >= 0 and visible < control.GetItemCount(): - control.EnsureVisible(visible) + control.ScrollHint.EnsureVisible(visible) return if 0 <= (row - top) < pn: - control.EnsureVisible( + control.ScrollHint.EnsureVisible( min(top + pn - 2, control.GetItemCount() - 1) ) elif row < top: - control.EnsureVisible( + control.ScrollHint.EnsureVisible( min(row + pn - 1, control.GetItemCount() - 1) ) else: - control.EnsureVisible(row) + control.ScrollHint.EnsureVisible(row) control.SetItemState( row, diff --git a/traitsui/wx/tests/test_font_trait.py b/traitsui/wx/tests/test_font_trait.py index 975eeb48d..bb91282e7 100644 --- a/traitsui/wx/tests/test_font_trait.py +++ b/traitsui/wx/tests/test_font_trait.py @@ -27,7 +27,7 @@ class FontExample(HasTraits): font = WxFont() -class TestPyQtFont(unittest.TestCase): +class TestWxFont(unittest.TestCase): def test_create_traitsfont(self): expected_outcomes = {} @@ -75,11 +75,11 @@ def test_create_traitsfont(self): # test we get expected font self.assertIsInstance(result, TraitsFont) - self.assert_qfont_equal(result, expected) + self.assert_wxfont_equal(result, expected) # round-trip trhough font_to_str result_2 = create_traitsfont(font_to_str(result)) - self.assert_qfont_equal(result, result_2) + self.assert_wxfont_equal(result, result_2) def test_create_traitsfont_wx_font(self): font = wx.Font( @@ -88,14 +88,14 @@ def test_create_traitsfont_wx_font(self): traits_font = create_traitsfont(font) self.assertIsInstance(traits_font, TraitsFont) - self.assert_qfont_equal(traits_font, font) + self.assert_wxfont_equal(traits_font, font) def test_create_traitsfont_system_default(self): font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) traits_font = create_traitsfont(font) self.assertIsInstance(traits_font, TraitsFont) - self.assert_qfont_equal(traits_font, font) + self.assert_wxfont_equal(traits_font, font) def test_create_traitsfont_pyface_font(self): args = simple_parser("18 pt Bold Oblique Underline Courier") @@ -103,13 +103,13 @@ def test_create_traitsfont_pyface_font(self): traits_font = create_traitsfont(font) self.assertIsInstance(traits_font, TraitsFont) - self.assert_qfont_equal(traits_font, font.to_toolkit()) + self.assert_wxfont_equal(traits_font, font.to_toolkit()) def test_font_trait_default(self): obj = FontExample() self.assertIsInstance(obj.font, TraitsFont) - self.assert_qfont_equal(obj.font, wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)) + self.assert_wxfont_equal(obj.font, wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)) def test_font_trait_str(self): obj = FontExample(font="18 pt Bold Oblique Underline Comic Sans script") @@ -118,7 +118,7 @@ def test_font_trait_str(self): ) self.assertIsInstance(obj.font, TraitsFont) - self.assert_qfont_equal(obj.font, wx_font) + self.assert_wxfont_equal(obj.font, wx_font) def test_font_trait_wx_font(self): wx_font = TraitsFont( @@ -127,7 +127,7 @@ def test_font_trait_wx_font(self): obj = FontExample(font=wx_font) self.assertIsInstance(obj.font, TraitsFont) - self.assert_qfont_equal(obj.font, wx_font) + self.assert_wxfont_equal(obj.font, wx_font) def test_font_trait_pyface_font(self): args = simple_parser("18 pt Bold Oblique Underline Courier typewriter") @@ -135,7 +135,7 @@ def test_font_trait_pyface_font(self): obj = FontExample(font=font) self.assertIsInstance(obj.font, TraitsFont) - self.assert_qfont_equal(obj.font, font.to_toolkit()) + self.assert_wxfont_equal(obj.font, font.to_toolkit()) def test_font_trait_none(self): obj = FontExample(font=None)