diff --git a/.coveragerc b/.coveragerc deleted file mode 100755 index bdb9d85b8..000000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] - branch = True - source = Stoner - relative_files = True diff --git a/.github/workflows/build_conda.yaml b/.github/workflows/build_conda.yaml index e2adc41bf..ef8b8b358 100755 --- a/.github/workflows/build_conda.yaml +++ b/.github/workflows/build_conda.yaml @@ -1,35 +1,35 @@ - -name: Conda - -on: - release: - types: ['released', 'prereleased'] - -# workflow_dispatch: # Un comment line if you also want to trigger action manually - -jobs: - conda_deployment_with_new_tag: - name: Conda deployment of package for platform ${{ matrix.os }} with Python ${{ matrix.python-version }} - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - name: Conda environment creation and activation - uses: conda-incubator/setup-miniconda@505e6394dae86d6a5c7fbb6e3fb8938e3e863830 - with: - python-version: "3.12" - environment-file: recipe/build-env.yml - activate-environment: build-environment - auto-update-conda: true - auto-activate-base: false - show-channel-urls: true - - name: Build and upload the conda packages - uses: uibcdf/action-build-and-upload-conda-packages@4940704d2be7906d3bda5b00e3c3e4472fd7808f - with: - meta_yaml_dir: recipe - overwrite: true - python-version: "3.12" - user: phygbu - label: main - token: ${{ secrets.ANACONDA }} # Replace with the right name of your secret + +name: Conda + +on: + release: + types: ['released', 'prereleased'] + +# workflow_dispatch: # Un comment line if you also want to trigger action manually + +jobs: + conda_deployment_with_new_tag: + name: Conda deployment of package for platform ${{ matrix.os }} with Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Conda environment creation and activation + uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 + with: + environment-file: recipe/build-env.yml + environment-name: build-environment + create-args: python=3.12 + init-shell: bash + - name: Build and upload the conda packages + uses: uibcdf/action-build-and-upload-conda-packages@4940704d2be7906d3bda5b00e3c3e4472fd7808f + with: + meta_yaml_dir: recipe + overwrite: true + python-version: "3.12" + user: phygbu + label: main + token: ${{ secrets.ANACONDA }} # Replace with the right name of your secret diff --git a/.github/workflows/run-tests-action.yaml b/.github/workflows/run-tests-action.yaml index 9aeffcb4f..f1742b35a 100755 --- a/.github/workflows/run-tests-action.yaml +++ b/.github/workflows/run-tests-action.yaml @@ -1,92 +1,100 @@ -name: pytest -on: push -jobs: - run_pytest: - name: run-tests (${{ matrix.python-version }}, ${{ matrix.os }}) - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash -l {0} - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12","3.13", "3.14"] - os: ["ubuntu-latest"] - steps: - - name: Check out repository code - uses: actions/checkout@v4 - - name: Install Conda environment with Micromamba - uses: conda-incubator/setup-miniconda@v3 - with: - environment-file: tests/test-env.yml - python: ${{ matrix.python-version }} - channels: conda-forge,phygbu - channel-priority: flexible - activate-environment: test-environment - auto-activate-base: false - - name: Conda information - run: | - conda info - conda list - conda config --show-sources - conda config --show - - name: install package - run: pip install --no-deps . - - name: Install headless server - run: | - sudo apt-get update - sudo apt-get install xvfb - sudo apt-get install qtbase5-dev - - name: Test with xvfb - run: | - xvfb-run --auto-servernum /usr/share/miniconda/envs/test-environment/bin/pytest -n 2 --cov-report= --cov=Stoner --junitxml pytest.xml - coverage xml - env: - TZ: Europe/London - LC_CTYPE: en_GB.UTF-8 - GH_ACTION: True - - name: Cleanup X11 server - uses: bcomnes/cleanup-xvfb@v1 - - name: Coveralls Parallel - uses: coverallsapp/github-action@v2 - with: - flag-name: run-${{ join(matrix.*, '-') }} - format: cobertura - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Unit Test Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: Unit Test Results (Python ${{ matrix.python-version }}) - path: pytest.xml - - name: Post Coveraage result to Codacy - run: | - export CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} - bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage.xml - - publish-test-results: - name: "Publish Unit Tests Results" - needs: run_pytest - runs-on: ubuntu-latest - if: always() - - steps: - - name: Download Artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - with: - files: artifacts/**/*.xml - - coverage-finish: - needs: run_pytest - runs-on: ubuntu-latest - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@v2 - with: - parallel-finished: true - carryforward: "run-1,run-2" +name: pytest +on: push +jobs: + run_pytest: + name: run-tests (${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + permissions: + contents: read + checks: write + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12","3.13", "3.14"] + os: ["ubuntu-latest"] + steps: + - name: Check out repository code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Install Mamba environment + uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 + with: + environment-file: tests/test-env.yml + environment-name: test-environment + create-args: >- + python=${{ matrix.python-version }} + --channel-priority flexible + init-shell: bash + cache-downloads: true + cache-environment: true + - name: Mamba information + run: | + micromamba info + micromamba list + - name: install package + run: pip install --no-deps . + - name: Install headless server + run: | + sudo apt-get update + sudo apt-get install xvfb + sudo apt-get install qtbase5-dev + - name: Test with xvfb + run: | + xvfb-run --auto-servernum pytest -n 2 --cov-report= --cov=Stoner --junitxml pytest.xml + coverage xml + env: + TZ: Europe/London + LC_CTYPE: en_GB.UTF-8 + GH_ACTION: True + - name: Cleanup X11 server + uses: bcomnes/cleanup-xvfb@9e016c43bb8d73fe7d5933d2ef00fd770c1a7c50 # v1.0.9 + - name: Coveralls Parallel + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + flag-name: run-${{ join(matrix.*, '-') }} + format: cobertura + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Unit Test Results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: Unit Test Results (Python ${{ matrix.python-version }}) + path: pytest.xml + - name: Post Coveraage result to Codacy + run: | + export CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} + bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage.xml + + publish-test-results: + name: "Publish Unit Tests Results" + needs: run_pytest + runs-on: ubuntu-latest + if: always() + permissions: + checks: write + pull-requests: write + + steps: + - name: Download Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: artifacts + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0 + with: + files: artifacts/**/*.xml + + coverage-finish: + needs: run_pytest + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + parallel-finished: true + carryforward: "run-1,run-2" diff --git a/.idea/PythonCode.iml b/.idea/PythonCode.iml new file mode 100755 index 000000000..d9e6024fc --- /dev/null +++ b/.idea/PythonCode.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100755 index 000000000..a5ae99b32 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.ipynb_checkpoints/Makefile-checkpoint b/.ipynb_checkpoints/Makefile-checkpoint new file mode 100755 index 000000000..56dcc6bf5 --- /dev/null +++ b/.ipynb_checkpoints/Makefile-checkpoint @@ -0,0 +1,57 @@ +ifeq ($(OS),"Windows_NT") + SPHINX_BUILD = sphinx-build.bat +else + SPHINX_BUILD = sphinx-build +endif +PYTHON_SETUP = python setup.py + +BRANCH = `git branch | grep '*' | cut -d ' ' -f 2` + +clean: + $(MAKE) -C doc clean + - rm dist/* + - rm -rf build/* + - find -name '__pycache__' -exec rm -rf {} \; + +test: + pytest -n `python -c 'import os;print(min(8,os.cpu_count()))'` + +check: + prospector -E -0 --profile-path=. -P .landscape.yml Stoner > prospector-report.txt + +black: + find Stoner -name '*.py' | xargs -d "\n" black -l 119 + find doc/samples -name '*.py' | xargs -d "\n" black -l 80 + find scripts -name '*.py' | xargs -d "\n" black -l 80 + +commit: black + $(MAKE) -C doc readme + git add tests + git add Stoner + git add doc/samples + git commit -a + git push origin $(BRANCH) + +_build_wheel: + $(MAKE) -C doc readme + $(PYTHON_SETUP) sdist bdist_wheel --universal + twine upload dist/* + +wheel: clean test _build_wheel + +docbuild: FORCE + $(MAKE) -C doc clean + $(MAKE) -C doc html + ( cd ../gh-pages; git pull ) + rsync -rcm --perms --chmod=ugo=rwX --delete --filter="P .git" --filter="P .nojekyll" doc/_build/html/ ../gh-pages/ + +conda: + conda build --python=3.6 recipe & + conda build --python=3.7 recipe & + conda build --python 3.8 recipe & + + +rtdbuild: export READTHEDOCS=1 +rtdbuild: docbuild + +FORCE: diff --git a/.ipynb_checkpoints/README-checkpoint.rst b/.ipynb_checkpoints/README-checkpoint.rst new file mode 100755 index 000000000..0e6a96796 --- /dev/null +++ b/.ipynb_checkpoints/README-checkpoint.rst @@ -0,0 +1,257 @@ +.. image:: https://travis-ci.com/stonerlab/Stoner-PythonCode.svg?branch=master + :target: https://travis-ci.com/stonerlab/Stoner-PythonCode + +.. image:: https://coveralls.io/repos/github/stonerlab/Stoner-PythonCode/badge.svg?branch=master + :target: https://coveralls.io/github/stonerlab/Stoner-PythonCode?branch=master + +.. image:: https://api.codacy.com/project/badge/Grade/372f2f9227134fab9c6c2f2ba83962ba + :target: https://www.codacy.com/app/stonerlab/Stoner-PythonCode?utm_source=github.com&utm_medium=referral&utm_content=stonerlab/Stoner-PythonCode&utm_campaign=Badge_Grade + +.. image:: https://badge.fury.io/py/Stoner.svg + :target: https://badge.fury.io/py/Stoner + +.. image:: https://anaconda.org/phygbu/stoner/badges/version.svg + :target: https://anaconda.org/phygbu/stoner + +.. image:: https://readthedocs.org/projects/stoner-pythoncode/badge/?version=latest + :target: http://stoner-pythoncode.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. image:: https://zenodo.org/badge/10057055.svg + :target: https://zenodo.org/badge/latestdoi/10057055 + + +Introduction +============ + + +The *Stoner* Python package is a set of utility classes for writing data analysis code. It was written within +the Condensed Matter Physics group at the University of Leeds as a shared resource for quickly writing simple +programs to do things like fitting functions to data, extract curve parameters, churn through large numbers of +small text data files and work with certain types of scientific image files. + +For a general introduction, users are referred to the Users Guide, which is part of the `online documentation`_ along with the +API Reference guide. The `github repository`_ also contains some example scripts. + +Getting this Code +================== + +.. image:: https://i.imgur.com/h4mWwM0.png + :target: https://www.youtube.com/watch?v=uZ_yKs11W18 + :alt: Introduction and Installation Guide to Stoner Pythin Package + :width: 320 + +The *Stoner* package requires h5py>=2.7.0, lmfit>=0.9.7, matplotlib>=2.0,numpy>=1.13, Pillow>=4.0, +scikit-image>=0.13.0 & scipy>=1.0.0 and also optional depends on filemagic, npTDMS, imreg_dft and numba. + +Ananconda Python (and probably other scientific Python distributions) include nearly all of the dependencies, and the remaining +dependencies are collected together in the **phygbu** repositry on anaconda cloud. The easiest way to install the Stoner package is, +therefore, to install the most recent Anaconda Python distribution. + +Compatibility +-------------- + +Versions 0.9.x (stable branch) are compatible with Python 2.7, 3.5, 3.6 and 3.7. The latest 0.9.6 version is also compatible with Python 3.8 +The development verstion (0.10, master branch) is compatible with Python 3.6, 3.7 and 3.8 but not 2.7. + +Conda packages are prepared for the stable branch and when the development branch enters beta testing. Pip wheels are prepared for selected stable releases only. + +Installation +------------ + +After installing the current Anaconda version, open a terminal (Mac/Linux) or Anaconda Prompt (Windows) an do: + +.. code-block:: sh + + conda install -c phygbu Stoner + +If you are not using Anaconda python, then pip should also work: + +.. code-block:: sh + + pip install Stoner + +This will install the Stoner package and any missing dependencies into your current Python environment. Since the package is under fairly +constant updates, you might want to follow the development with git. The source code, along with example scripts +and some sample data files can be obtained from the github repository: https://github.com/stonerlab/Stoner-PythonCode + +Overview +======== + +The main part of the **Stoner** package provides four basic top-level classes that describe: + - an individual file of experimental data (**Stoner.Data**), + - an individual experimental image (**Stoner.ImageFile**), + - a list (such as a directory tree on disc) of many experimental files (**Stoner.DataFolder**) + - a list (such as a directory tree on disc) of many image files (**Stoner.ImageFolder**). + +For our research, a typical single experimental data file is essentially a single 2D table of floating point +numbers with associated metadata, usually saved in some ASCII text format. This seems to cover most experiments +in the physical sciences, but it you need a more complex format with more dimensions of data, we suggest +you look elsewhere. + +Increasingly we seem also to need process image files and so partnering the experimental measurement file classes, +we have a parallel set of classes for interacting with image data. + +The general philosophy used in the package is to work with data by using methods that transform the data in place. +Additionally, many of the analysis methods pass a copy of the data as their return value, allowing a sequence of +operations to be chained together in one line. + +This is a *data-centric* approach - we have some data and we do various operations on it to get to our result. In +contrasr, traditional functional programming thinks in terms of various functions into which you pass data. + + +Data and Friends +---------------- + +**Stoner.Data** is the core class for representing individual experimental data sets. +It is actually composed of several mixin classes that provide different functionality, with methods +to examine and manipulate data, manage metadata, load and save data files, plot results and carry out various analysis tasks. +It has a large number of sub classes - most of these are in Stoner.formats and are used to handle the loading of specific +file formats. + +ImageFile +--------- + +**Stoner.ImageFile** is the top-level class for managing image data. It is the equivalent of **Stoner.Data** and maintains +metadta and comes with a number of methods to manipulate image data. The image data is stored internally as a masked numpy +array and where possible the masking is taken into account when carrying out image analysis tasks. Through some abuse of +the Python class system, functions in the scpy.ndimage and scikit-image modules are mapped into methods of the ImageFile +class allowing a very rich set of operations on the data sets. The default IO methods handle tiff and png images and can +store the metadata of the ImageFile within those file formats. + +DataFolder +---------- + +**Stoner.DataFolder** is a class for assisting with the work of processing lots of files in a common directory +structure. It provides methods to list. filter and group data according to filename patterns or metadata and then to execute +a function on each file or group of files and then collect metadata from each file in turn. A key feature of DataFolder is +its ability to work with the collated metadata from the individual files that are held in the DataFolder. +In combination with its ability to walk through a complete heirarchy of groups of +**Data** objects, the handling of the common metadata provides powerful tools for quickly writing data reduction scripts. + +ImageFolder +----------- + +**Stoner.ImageFolder** is the equivalent of DataFolder but for images (although technically a DataFolder can contain ImageFile +objects, the ImageFolder class offers additional Image specific functionality). There is a subclass of ImageFolder, +**Stoner.Image.ImageStack** that uses a 3D numpy array as it's primary image store which permits faster access +(at the expense of a larger memory footprint) than the lazy loading ordered dictionary of **ImageFolder** + +Other Modules and Classes +------------------------- + +The **Stoner.HDF5** module provides some additional classes to manipulate *Data* and *DataFolder* objects within HDF5 +format files. HDF5 is a common chouse for storing data from large scale facilties, although providing a way to handle +arbitary HDF5 files is beyond the scope of this package at this time - the format is much too complex and flexible to make that +an easy task. Rather it provides a way to work with large numbers of experimental sets using just a single file which may be less +brutal to your computer's OS than having directory trees with millions of individual files. + +The module also provides some classes to support loading some particular HDF5 flavoured files into **Data** and **ImageFile** +objects. + +The **Stoner.Zip** module provides a similar set of classes to **Stoner.HDF5** but working with the ubiquitous zip compressed file format. + +Resources +========== + +Included in the `github repository`_ are a (small) collection of sample scripts +for carrying out various operations and some sample data files for testing the loading and processing of data. There is also a +`User_Guide`_ as part of this documentation, along with a :doc:`complete API reference ` + +Contact and Licensing +===================== + +The lead developer for this code is `Dr Gavin Burnell`_ , but many current and former members of +the CM Physics group have contributed code, ideas and bug testing. + +The User Guide gives the current list of other contributors to the project. + +This code and the sample data are all (C) The University of Leeds 2008-2021 unless otherwise indficated in the source +file. The contents of this package are licensed under the terms of the GNU Public License v3 + +Recent Changes +============== + +Current PyPi Version +-------------------- + +The current version of the package on PyPi will be the stable branch until the development branch enters beta testing, when we start +making beta packages available. + +Development Version +------------------- + +The current development version is hosted in the master branch of the repository and will become version 0.10. + +New Features in 0.10-dev include: + + * Refactor Stoner.Core.DataFile to move functionality to mixin classes + * Start implementing PEP484 Type hinting + * Support pathlib for paths + * Switch from Tk based dialogs to Qt5 ones + * Refactoring the **baseFolder** class so that sub-groups are stored in an attribute that is an instance of a custom + dictionary with methods to prune and filter in the virtual tree of sub-folders. + * Refactoring of the **ImageArray** and **ImageFile** so that binding of external functions as methods is done at + class definition time rather than at runtime with overly complex __getattr__ methods. The longer term goal is to + depricate the use of ImageArray in favour of just using ImageFile. + * Introduce interactive selection of boxes, lines and mask regions for interactive Matplotlib backends. + +Build Status +~~~~~~~~~~~~ + +Version 0.7 onwards are tested using the Travis-CI services with unit test coverage assessed by Coveralls. + +Version 0.9 is tested with Python 2.7, 3.5, 3.6 using the standard unittest module. + +The development version - which will be 0.10 is tested using **pytest** with Python 3.6, Python 3.7 and Python 3.8. + + +Citing the Stoner Package +~~~~~~~~~~~~~~~~~~~~~~~~~ + +We maintain a digital object identifier (doi) for this package (linked to on the status bar at the top of this readme) and +encourage any users to cite this package via that doi. + +Stable Versions +--------------- + +Online documentation for all versions can be found on the ReadTheDocs pages `online documentation`_ + +Version 0.9 is the current stable version. This is the last version to support Python 2 and 3<3.6. Features of this release are: + + * Refactoring of the package into a more granual core, plot, formats, folders packages with submodules + * Overhaul of the documentation and user guide + * Dropping support for the older Stoner.Image.stack.ImageStack class + * Droppping support for matplotlib<2.0 + * Support for Python 3.7 (and 3.8 from 0.9.6) + * Unit tests now > 80% coverage across the package. + +Version 0.8 is the previous stable release. The main new features were: + + * Reworking of the ImageArray, ImageFile and ImageFolder with many updates and new features. + * New mixin based ImageStack2 that can manipulate a large number of images in a 3D numpy array + * Continued re-factoring of DataFolder using the mixin approach + * Further increases to unit-test coverage, bug fixes and refactoring of some parts of the code. + * _setas objects implement a more complete MutableMapping interface and also support +/- operators. + * conda packages now being prepared as the preferred package format + +0.8.2 was the final release of the 0.8.0 branch + +The old stable version is 0.7.2. Features of 0.7.2 include + + * Replace older AnalyseFile and PlotFile with mixin based versions AnalysisMixin and PlotMixin + * Addition of Stoner.Image package to handle image analysis + * Refactor DataFolder to use Mixin classes + * DataFolder now defaults to using :py:class:`Stoner.Core.Data` + * DataFolder has an options to skip iterating over empty Data files + * Further improvements to :py:attr:`Stoner.Core.DataFile.setas` handline. + +No further relases will be made to 0.7.x. + +0.6, 0.7 should work on Python 2.7 and 3.5 +0.8 is also tested on Python 3.6 + +.. _online documentation: http://stoner-pythoncode.readthedocs.io/en/latest/ +.. _github repository: http://www.github.com/stonerlab/Stoner-PythonCode/ +.. _Dr Gavin Burnell: http://www.stoner.leeds.ac.uk/people/gb +.. _User_Guide: http://stoner-pythoncode.readthedocs.io/en/latest/UserGuide/ugindex.html \ No newline at end of file diff --git a/.spyproject/config/backups/codestyle.ini.bak b/.spyproject/config/backups/codestyle.ini.bak new file mode 100755 index 000000000..71eedf943 --- /dev/null +++ b/.spyproject/config/backups/codestyle.ini.bak @@ -0,0 +1,8 @@ +[codestyle] +indentation = True +edge_line = True +edge_line_columns = 79 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/backups/encoding.ini.bak b/.spyproject/config/backups/encoding.ini.bak new file mode 100755 index 000000000..44707b046 --- /dev/null +++ b/.spyproject/config/backups/encoding.ini.bak @@ -0,0 +1,6 @@ +[encoding] +text_encoding = utf-8 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/backups/vcs.ini.bak b/.spyproject/config/backups/vcs.ini.bak new file mode 100755 index 000000000..6b8d7a36d --- /dev/null +++ b/.spyproject/config/backups/vcs.ini.bak @@ -0,0 +1,7 @@ +[vcs] +use_version_control = False +version_control_system = + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/backups/workspace.ini.bak b/.spyproject/config/backups/workspace.ini.bak new file mode 100755 index 000000000..17dc6fa0c --- /dev/null +++ b/.spyproject/config/backups/workspace.ini.bak @@ -0,0 +1,12 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False +project_type = 'empty-project-type' +recent_files = ['Stoner\\folders\\methods.py', 'scripts\\VSManalysis_v2.py', 'Stoner\\tools\\widgets.py', 'tests\\stoner\\tools\\test_widgets.py', 'Stoner\\core\\base.py', 'Stoner\\analysis\\utils.py'] + +[main] +version = 0.2.0 +recent_files = [] + diff --git a/.spyproject/config/codestyle.ini b/.spyproject/config/codestyle.ini new file mode 100755 index 000000000..71eedf943 --- /dev/null +++ b/.spyproject/config/codestyle.ini @@ -0,0 +1,8 @@ +[codestyle] +indentation = True +edge_line = True +edge_line_columns = 79 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini b/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini new file mode 100755 index 000000000..3772dc99e --- /dev/null +++ b/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini @@ -0,0 +1,5 @@ +[codestyle] +indentation = True +edge_line = True +edge_line_columns = 79 + diff --git a/.spyproject/config/defaults/defaults-encoding-0.2.0.ini b/.spyproject/config/defaults/defaults-encoding-0.2.0.ini new file mode 100755 index 000000000..d76cad0db --- /dev/null +++ b/.spyproject/config/defaults/defaults-encoding-0.2.0.ini @@ -0,0 +1,3 @@ +[encoding] +text_encoding = utf-8 + diff --git a/.spyproject/config/defaults/defaults-vcs-0.2.0.ini b/.spyproject/config/defaults/defaults-vcs-0.2.0.ini new file mode 100755 index 000000000..782612f7d --- /dev/null +++ b/.spyproject/config/defaults/defaults-vcs-0.2.0.ini @@ -0,0 +1,4 @@ +[vcs] +use_version_control = False +version_control_system = + diff --git a/.spyproject/config/defaults/defaults-workspace-0.2.0.ini b/.spyproject/config/defaults/defaults-workspace-0.2.0.ini new file mode 100755 index 000000000..d4bbff1bb --- /dev/null +++ b/.spyproject/config/defaults/defaults-workspace-0.2.0.ini @@ -0,0 +1,6 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False + diff --git a/.spyproject/config/encoding.ini b/.spyproject/config/encoding.ini new file mode 100755 index 000000000..44707b046 --- /dev/null +++ b/.spyproject/config/encoding.ini @@ -0,0 +1,6 @@ +[encoding] +text_encoding = utf-8 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/vcs.ini b/.spyproject/config/vcs.ini new file mode 100755 index 000000000..6b8d7a36d --- /dev/null +++ b/.spyproject/config/vcs.ini @@ -0,0 +1,7 @@ +[vcs] +use_version_control = False +version_control_system = + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/workspace.ini b/.spyproject/config/workspace.ini new file mode 100755 index 000000000..17dc6fa0c --- /dev/null +++ b/.spyproject/config/workspace.ini @@ -0,0 +1,12 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False +project_type = 'empty-project-type' +recent_files = ['Stoner\\folders\\methods.py', 'scripts\\VSManalysis_v2.py', 'Stoner\\tools\\widgets.py', 'tests\\stoner\\tools\\test_widgets.py', 'Stoner\\core\\base.py', 'Stoner\\analysis\\utils.py'] + +[main] +version = 0.2.0 +recent_files = [] + diff --git a/Stoner/__init__.py b/Stoner/__init__.py index eb586de48..b33e05ff4 100755 --- a/Stoner/__init__.py +++ b/Stoner/__init__.py @@ -37,8 +37,8 @@ Options = _Options() -__version_info__ = ("0", "11", "1") -__version__ = ".".join(__version_info__) +__version__ = "0.11.1" +__version_info__ = tuple(__version__.split(".")) __homepath__ = pathlib.Path(__file__).parent.resolve() __datapath__ = (__homepath__ / ".." / "sample-data").resolve() diff --git a/Stoner/folders/core.py b/Stoner/folders/core.py index 84339c316..47fc807a5 100755 --- a/Stoner/folders/core.py +++ b/Stoner/folders/core.py @@ -220,6 +220,7 @@ def __new__(cls, *args, **kwargs): self._root = "." self._default_store = None self.directory = None + self.executor = None return self def __init__(self, *args, **kwargs): @@ -654,6 +655,11 @@ def __clone__(self, other=None, attrs_only=False): ######## Methods to implement the MutableMapping abstract methods ######### ######## And to provide a mapping interface that mainly access groups ##### + def __del__(self): + """Clean up the exececutor if it is defined.""" + if self.executor: + self.executor.shutdown() + def __getitem__(self, name): """Try to get either a group or an object. diff --git a/Stoner/tools/file.py b/Stoner/tools/file.py index e4e9e864a..f0ae36bca 100755 --- a/Stoner/tools/file.py +++ b/Stoner/tools/file.py @@ -67,7 +67,13 @@ def test_is_zip(filename, member=""): """ if not filename or str(filename) == "": return False - if zipfile.is_zipfile(filename): + if isinstance(filename, (bytes, bytearray)) and b"\x00" in filename: + return False + try: + is_zip = zipfile.is_zipfile(filename) + except (ValueError, TypeError, OSError): + return False + if is_zip: return filename, member part = os.path.basename(filename) newfile = os.path.dirname(filename) diff --git a/doc/UserGuide/install.rst b/doc/UserGuide/install.rst index 5ebce8e2b..35a0984d0 100755 --- a/doc/UserGuide/install.rst +++ b/doc/UserGuide/install.rst @@ -55,9 +55,9 @@ machine then:: should install the current master branch. Otherwise download the zip file from the github site and do:: - python setup.py install + pip install . -to insatall it locally. +to install it locally. Using the Development Version of the Stoner Package =================================================== diff --git a/doc/conf.py b/doc/conf.py index 0b0f5b41e..7edda995d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -71,10 +71,13 @@ # |version| and |release|, also used in various other places throughout the # built documents. # +try: + from importlib.metadata import PackageNotFoundError, version as _pkg_version + release = _pkg_version("Stoner") +except PackageNotFoundError: + release = "unknown" # The short X.Y version. -version = '1' -# The full version, including alpha/beta/rc tags. -release = '1' +version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6c25c2b82 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "Stoner" +dynamic = ["version"] +description = "Library to help write data analysis tools for experimental condensed matter physics." +readme = {file = "README.rst", content-type = "text/x-rst"} +requires-python = ">=3.11" +license = {text = "GPLv3"} +authors = [ + {name = "Gavin Burnell", email = "g.burnell@leeds.ac.uk"}, +] +keywords = ["Data-Analysis", "Physics"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Physics", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", +] +dependencies = [ + "scipy>=1.14", + "numpy>=2.0", + "matplotlib>=3.8", + "scikit-image>=0.24", + "h5py", + "cycler>=0.10.0", + "filemagic>=1.6", + "image-registration>=0.2.1", + "lmfit>=1.3", + "memoization>=0.1.4", + "npTDMS>=1.9.0", + "python-dateutil>=2.8.0", + "statsmodels", + "tabulate>=0.9", + "imreg_dft>=2.0", + "multiprocess>=0.70", + "dill>=0.2.8", + "urllib3>=1.26", + "seaborn>=0.13", + "looseversion>=1.0", + "chardet>=5.2.0", +] + +[project.optional-dependencies] +PrettyPrint = ["tabulate>=0.7.5"] +mimetype_detection = ["magic"] +TDMS = ["nptdms"] +numba = ["numba"] +cv2 = ["opencv-python"] +image_alignment = ["imreg_dft", "image_registration"] + +[project.urls] +Homepage = "https://github.com/gb119/Stoner-PythonCode" +Repository = "https://github.com/gb119/Stoner-PythonCode" + +[tool.setuptools.dynamic] +version = {attr = "Stoner.__version__"} + +[tool.setuptools.packages.find] +where = ["."] +include = ["Stoner*"] + +[tool.setuptools.package-data] +Stoner = ["stylelib/*.mplstyle"] + +[tool.coverage.run] +branch = true +source = ["Stoner"] +relative_files = true + +[tool.pydocstyle] +ignore = ["D203"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/recipe/build-env.yml b/recipe/build-env.yml index 0e2dd43ad..fb10f33ee 100755 --- a/recipe/build-env.yml +++ b/recipe/build-env.yml @@ -6,6 +6,7 @@ channels: dependencies: - conda-build - anaconda-client - - setuptools + - setuptools >=61.0 - wheel + - build - twine diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 995fa2f32..e5d24e432 100755 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -1,11 +1,10 @@ -{% set data = load_setup_py_data() %} +{% set version_match = load_file_regex(load_file="Stoner/__init__.py", regex_pattern='__version__\s*=\s*"([^"]+)"') %} +{% set version = version_match[1] %} {% set name = "Stoner" %} -{% set file_ext = "tar.gz" %} - package: name: '{{ name|lower }}' - version: {{ data.get('version') }} + version: {{ version }} source: path: .. @@ -13,13 +12,13 @@ source: build: noarch: python number: 1 - script: {{ PYTHON }} setup.py install --single-version-externally-managed --record=record.txt + script: {{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation requirements: build: - python >=3.11 - - pytest - - pytest-runner + - pip + - setuptools >=61.0 run: - python >=3.11 diff --git a/setup.cfg b/setup.cfg index 85a389711..65aabd5e6 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,2 @@ -[build_sphinx] -source-dir = doc/ -build-dir = doc/_build -all_files = 1 - -[upload_sphinx] -upload-dir = doc/_build/html - -[bdist_wheel] -universal=1 - -[aliases] -test=pytest - [pydocstyle] ignore = D203 diff --git a/setup.py b/setup.py deleted file mode 100755 index 552943be2..000000000 --- a/setup.py +++ /dev/null @@ -1,131 +0,0 @@ -import io -import os -import re -import sys -from os import environ as env - -from setuptools import find_packages, setup - - -def get_version(): - init_name=os.path.join(os.path.dirname(__file__),"Stoner","__init__.py") - with open(init_name,"r") as init: - for line in init: - line=line.strip() - if line.startswith("__version_info__"): - parts=line.split("=") - __version_info__=eval(parts[1].strip()) - return '.'.join(__version_info__) - raise ValueError(f"Failed to get version info from {init_name}") - -def yield_sphinx_only_markup(lines): - """ - :param file_inp: a `filename` or ``sys.stdin``? - :param file_out: a `filename` or ``sys.stdout`?` - - """ - substs = [ - ## Selected Sphinx-only Roles. - # - (r':abbr:`([^`]+)`', r'\1'), - (r':ref:`([^`]+)`', r'`\1`_'), - (r':term:`([^`]+)`', r'**\1**'), - (r':dfn:`([^`]+)`', r'**\1**'), - (r':(samp|guilabel|menuselection):`([^`]+)`', r'``\2``'), - (r':py:[a-z]+:`([^`]+)`', r'\1'), - - - - ## Sphinx-only roles: - # :foo:`bar` --> foo(``bar``) - # :a:foo:`bar` XXX afoo(``bar``) - # - #(r'(:(\w+))?:(\w+):`([^`]*)`', r'\2\3(``\4``)'), - (r':(\w+):`([^`]*)`', r'\1(``\2``)'), - - - ## Sphinx-only Directives. - # - (r'\.\. doctest', r'code-block'), - (r'\.\. plot::', r'.. '), - (r'\.\. seealso', r'info'), - (r'\.\. glossary', r'rubric'), - (r'\.\. figure::', r'.. '), - - - ## Other - # - (r'\|version\|', r'x.x.x'), - ] - - regex_subs = [ (re.compile(regex, re.IGNORECASE), sub) for (regex, sub) in substs ] - - def clean_line(line): - try: - for (regex, sub) in regex_subs: - line = regex.sub(sub, line) - except Exception as ex: - print("ERROR: %s, (line(%s)"%(regex, sub)) - raise ex - - return line - - for line in lines: - yield clean_line(line) - -def read(fname): - mydir=os.path.dirname(__file__) - with io.open(os.path.join(mydir, fname)) as fd: - return fd.readlines() - -def requires(fname): - mydir=os.path.dirname(__file__) - with io.open(os.path.join(mydir, fname)) as fd: - entries=fd.readlines() - entries=[entry for entry in entries if entry[0] not in " #\n\t"] - return entries - -if "READTHEDOCS" in env: - requyirements="doc/requirements.txt" -else: - requyirements="requirements.txt" - -setup( - name = "Stoner", - python_requires='>3.7', - version = str(get_version()), - author = "Gavin Burnell", - author_email = "g.burnell@leeds.ac.uk", - description = "Library to help write data analysis tools for experimental condensed matter physics.", - license = "GPLv3", - keywords = "Data-Analysis Physics", - url = "http://github.com/~gb119/Stoner-PythonCode", - packages=find_packages(), - package_dir={'Stoner': 'Stoner'}, - package_data={'Stoner':['stylelib/*.mplstyle']}, - test_suite="tests", - # setup_requires=['pytest-runner'], - # tests_require=['pytest'], - install_requires=requires(requyirements), - extras_require = { "PrettyPrint":["tabulate>=0.7.5"], - "mimetype_detection":["magic"], - "TDMS":["nptdms"], - "numba":["numba"], - "cv2":["cv2"], - "image_alignment":["imreg_dft","image_registration"]}, - long_description= ''.join(yield_sphinx_only_markup(read('README.rst'))), - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Physics", - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - ], -) diff --git a/tests/Stoner/Image/test_wdigets.py b/tests/Stoner/Image/test_wdigets.py deleted file mode 100755 index 6abc66eb4..000000000 --- a/tests/Stoner/Image/test_wdigets.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Test the Image Widgets used for selections - -@author: phygbu -""" -import os -import threading -import time - -import numpy as np -import pytest -from matplotlib.backend_bases import Event - -import Stoner -from Stoner.Image.widgets import send_event as _event - - -def _trigger(image): - time.sleep(5) - _event(image, "on_click", xdata=1, ydata=1, button=1) - for coord in np.linspace(1, 100, 51): - _event(image, "draw_line", xdata=coord, ydata=coord) - _event(image, "on_click", xdata=coord, ydata=coord, button=1) - - -def _trigger2(image): - time.sleep(5) - _event(image, "keypress", xdata=50, ydata=75, key="x") - _event(image, "on_click", xdata=50, ydata=75, button=1) - - -def _trigger3(image): - time.sleep(5) - _event(image, "keypress", xdata=50, ydata=75, key="y") - _event(image, "on_click", xdata=50, ydata=75, button=1) - - -def _trigger4(image): - time.sleep(5) - select = image._image._select - event1 = Event("fake", select.fig.canvas) - event1.xdata = 25 - event1.ydata = 25 - event2 = Event("fake", select.fig.canvas) - event2.xdata = 75 - event2.ydata = 50 - select.on_select(event1, event2) - time.sleep(2) - _event(image, "finish", key="enter") - - -def _trigger5(image, mode): - time.sleep(5) - _event(image, ["draw", "on_click"], xdata=50, ydata=25, button=1, dblclick=False) - _event(image, ["draw", "on_click"], xdata=75, ydata=50, button=1, dblclick=False) - _event(image, "keypress", xdata=50, ydata=75, key=mode.lower()[0]) - if mode == "c": # add some extra points: - _event(image, ["draw", "on_click"], xdata=30, ydata=40, button=1, dblclick=False) - _event(image, ["draw", "on_click"], xdata=30, ydata=30, button=1, dblclick=False) - _event(image, "keypress", xdata=50, ydata=75, key="i") - time.sleep(2) - _event(image, "keypress", xdata=50, ydata=75, key="enter") - - -def _trigger6(image, mode): - time.sleep(5) - _event(image, ["draw", "on_click"], xdata=50, ydata=25, button=1, dblclick=False) - _event(image, ["draw", "on_click"], xdata=75, ydata=50, button=1, dblclick=False) - time.sleep(2) - _event(image, "keypress", xdata=50, ydata=75, key="escape") - - -def test_profile_line(): - os.chdir(Stoner.__homepath__ / ".." / "sample-data") - img = Stoner.ImageFile("Sample_Image_2017-10-15_100.hdf5") - thread = threading.Thread(target=_trigger, args=(img,)) - thread.start() - result = img.profile_line() - assert len(result) == 142 - assert result.x.min() == 0.0 - assert np.isclose(result.x.max(), 140, atol=0.01) - assert np.isclose(result.y.mean(), 26548.72, atol=0.01) - thread = threading.Thread(target=_trigger2, args=(img,)) - thread.start() - result = img.profile_line() - assert len(result) == 101 - assert result.x.min() == 0.0 - assert result.x.max() == 100.0 - assert np.isclose(result.y.mean(), 20022.16, atol=0.01) - thread = threading.Thread(target=_trigger3, args=(img,)) - thread.start() - result = img.profile_line() - assert len(result) == 101 - assert result.x.min() == 0.0 - assert result.x.max() == 100.0 - assert np.isclose(result.y.mean(), 27029.16, atol=0.01) - - -def test_crop_with_ui(): - os.chdir(Stoner.__homepath__ / ".." / "sample-data") - img = Stoner.ImageFile("Sample_Image_2017-10-15_100.hdf5") - thread = threading.Thread(target=_trigger4, args=(img,)) - thread.start() - result = img.crop() - assert result.shape == (25, 50) - - -def test_mask_select(): - os.chdir(Stoner.__homepath__ / ".." / "sample-data") - img = Stoner.ImageFile("Sample_Image_2017-10-15_100.hdf5") - thread = threading.Thread(target=_trigger5, args=(img, "p")) - thread.start() - img.mask.select() - result = img.mask.sum() - assert result == 9324, f"Mask selection by polygon failed result={result}" - img.mask = False - thread = threading.Thread(target=_trigger5, args=(img, "c")) - thread.start() - img.mask.select() - result = img.mask.sum() - assert result in (3664, 7489), f"Mask selection by circle failed result={result}" - img.mask = False - thread = threading.Thread(target=_trigger5, args=(img, "r")) - thread.start() - img.mask.select() - result = img.mask.sum() - assert result == 8699, f"Mask selection by reverse failed result={result}" - img.mask = False - thread = threading.Thread(target=_trigger6, args=(img, "c")) - thread.start() - img.mask.select() - result = img.mask.sum() - assert result == 0, f"Cancelling selkection failed with result={result}" - - -if __name__ == "__main__": # Run some tests manually to allow debugging - pytest.main(["--pdb", __file__]) diff --git a/tests/Stoner/core/test_typeHintedDict.py b/tests/Stoner/core/test_typeHintedDict.py deleted file mode 100755 index c4738af25..000000000 --- a/tests/Stoner/core/test_typeHintedDict.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 8 15:20:15 2018 - -@author: phygbu -""" - -import os.path as path - -import pytest - -from Stoner.core.base import TypeHintedDict - -pth = path.dirname(__file__) -pth = path.realpath(path.join(pth, "../../")) -# sys.path.insert(0,pth) - - -def test_ops(): - d = TypeHintedDict([("el1", 1), ("el2", 2), ("el3", 3), ("other", 4)]) - d.filter("el") - assert len(d) == 3 - d.filter(lambda x: x.endswith("3")) - assert len(d) == 1 - assert d["el3"] == 3 - d["munge"] = None - assert d.types["munge"] == "Void", "Setting type for None value failed." - d["munge"] = 1 - assert d["munge{String}"] == "1", "Munging return type in getitem failed." - assert ( - repr(d) - == """'el3':I32:3 -'munge':I32:1""" - ), "Repr failed \n{}".format(d) - assert len(d | {"test": 2}) == len(d) + 1 - assert len(d | {"munge": 2}) == len(d) - e = d.copy() - e |= {"test": 4} - assert d != e - d.update({"test": 4}) - assert d == e - - -if __name__ == "__main__": # Run some tests manually to allow debugging - pytest.main(["--pdb", __file__]) diff --git a/tests/Stoner/test_Zip.py b/tests/Stoner/test_Zip.py deleted file mode 100755 index 51fc7a574..000000000 --- a/tests/Stoner/test_Zip.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test the ZIP module for zipfile compressed objects.""" - - -import os.path as path -import tempfile -import zipfile as zf -import numpy as np - -import pytest - -import Stoner - -pth = path.dirname(__file__) - -root = path.realpath(path.join(Stoner.__home__, "..")) -sample_data = path.realpath(path.join(root, "sample-data", "NLIV")) -tmpdir = tempfile.mkdtemp() - - -def test_zipFile(tmpdir): - d = Stoner.Data(Stoner.__datapath__ / "TDI_Format_RT.txt") - d.save(path.join(tmpdir, "TDI_Format_RT.zip"), filetype="ZippedFile") - - z2 = Stoner.Data(path.join(tmpdir, "TDI_Format_RT.zip")) - d["Loaded as"] = z2["Loaded as"] - d["Stoner.class"] = z2["Stoner.class"] - assert d == z2 - with zf.ZipFile(path.join(tmpdir, "TDI_Format_RT.zip"), "r") as open_zipfile: - z3 = Stoner.Data().load(open_zipfile, filetype="ZippedFile") - z3["Stoner.class"] = z2["Stoner.class"] - z3["Loaded as"] = z2["Loaded as"] - assert z2 == z3 - - -def test_zipfolder(): - # Test constructor from DataFolder - sf = Stoner.DataFolder(sample_data, pattern="*.txt") - szf = Stoner.folders.zip.ZipFolder(sf) - assert sf.shape == szf.shape, "ZipFolder created from DataFolder didn't keep the same shape" - assert sf[0] == szf[0], "First element of ZipFolder created from DataFolder changed!" - zipname = path.join(tmpdir, "test-zipfolder.zip") - szf.save(zipname) - assert sf.shape == szf.shape, "ZipFolder Changed shape when saving!" - szf_2 = Stoner.folders.zip.ZipFolder(zipname).compress() - assert szf_2.shape == szf.shape, "ZipFolder loaded from disc not same shape as ZipFolder in memory!" - fname = path.basename(szf[0].filename) - m1=szf[fname] - m2=szf_2[fname] - assert np.all(np.isclose(m1.data,m2.data)), "Data from two zip files is too different." - m2["Loaded from"]=m1["Loaded from"] - assert m1.metadata==m2.metadata, f"Metadata differes {m1.metadata^m2.metadata}" - - -if __name__ == "__main__": # Run some tests manually to allow debugging - pytest.main(["--pdb", __file__]) diff --git a/tests/test-env.yml b/tests/test-env.yml index 736314d68..158ed51e0 100755 --- a/tests/test-env.yml +++ b/tests/test-env.yml @@ -10,7 +10,6 @@ dependencies: - pytest - pytest-cov - pytest-forked - - pytest-runner - pytest-xdist - python-dateutil - pip