From 9a7529835d376a866d62f3bc12552e695bbce751 Mon Sep 17 00:00:00 2001 From: Maxime Liquet <35924738+maximlt@users.noreply.github.com> Date: Wed, 28 Feb 2024 21:24:15 +0100 Subject: [PATCH] Simplify dev tooling, remove `colorcet examples`, drop 3.7, add 3.12 (#120) * use mostly pip on the CI * remove colorcet examples cmd * move to pyproject * improve git archive * remove unused manifest * simplify building the docs * update dev instructions * drop 3.7 and try all python versions * simplify version definition --- .flake8 | 5 + .git_archival.txt | 4 + .gitattributes | 7 +- .github/workflows/build.yaml | 89 +- .github/workflows/docs.yaml | 33 +- .github/workflows/test.yaml | 74 +- .gitignore | 10 +- .pre-commit-config.yaml | 35 + MANIFEST.in | 8 - README.md | 12 +- assets/CET_to_py.py | 9 +- assets/rename_CET_maps.py | 1 - colorcet/__init__.py | 25 +- colorcet/__main__.py | 12 - colorcet/tests/baseline/test_matplotlib.png | Bin 12012 -> 12330 bytes .../baseline/test_matplotlib_glasbey.png | Bin 11774 -> 11693 bytes colorcet/tests/test_bokeh.py | 2 +- colorcet/version.py | 771 ------------------ conda.recipe/meta.yaml | 50 +- .../assets/images/census_fire.png | Bin .../assets/images/census_hot.png | Bin {examples => doc}/assets/images/fire.png | Bin {examples => doc}/assets/images/hot.png | Bin {examples => doc}/assets/images/jet.png | Bin {examples => doc}/assets/images/named.png | Bin {examples => doc}/assets/images/rainbow.png | Bin {examples => doc}/assets/images/rainbow4.png | Bin {examples => doc}/assets/write_named.py | 0 doc/conf.py | 7 +- doc/getting_started/index.rst | 16 +- doc/governance/project-docs/CONTRIBUTING.md | 4 +- doc/governance/project-docs/LICENSE.md | 2 +- {examples => doc}/index.ipynb | 17 +- doc/index.rst | 26 - .../user_guide/Categorical.ipynb | 2 + doc/user_guide/Categorical.rst | 16 - {examples => doc}/user_guide/Continuous.ipynb | 2 + doc/user_guide/Continuous.rst | 17 - {examples => doc}/user_guide/index.ipynb | 17 + doc/user_guide/index.rst | 25 - dodo.py | 38 - pyproject.toml | 84 +- setup.cfg | 5 - setup.py | 119 --- tox.ini | 58 -- 45 files changed, 306 insertions(+), 1296 deletions(-) create mode 100644 .flake8 create mode 100644 .git_archival.txt create mode 100644 .pre-commit-config.yaml delete mode 100644 MANIFEST.in delete mode 100644 colorcet/__main__.py delete mode 100644 colorcet/version.py rename {examples => doc}/assets/images/census_fire.png (100%) rename {examples => doc}/assets/images/census_hot.png (100%) rename {examples => doc}/assets/images/fire.png (100%) rename {examples => doc}/assets/images/hot.png (100%) rename {examples => doc}/assets/images/jet.png (100%) rename {examples => doc}/assets/images/named.png (100%) rename {examples => doc}/assets/images/rainbow.png (100%) rename {examples => doc}/assets/images/rainbow4.png (100%) rename {examples => doc}/assets/write_named.py (100%) rename {examples => doc}/index.ipynb (95%) delete mode 100644 doc/index.rst rename {examples => doc}/user_guide/Categorical.ipynb (99%) delete mode 100644 doc/user_guide/Categorical.rst rename {examples => doc}/user_guide/Continuous.ipynb (99%) delete mode 100644 doc/user_guide/Continuous.rst rename {examples => doc}/user_guide/index.ipynb (95%) delete mode 100644 doc/user_guide/index.rst delete mode 100644 dodo.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 tox.ini diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..5aaf5ef --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +include = *.py +exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,.ipynb_checkpoints,run_test.py +ignore = E, + W diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..8fb235d --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes index 7245958..4f9ccb6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ -colorcet/__init__.py export-subst -setup.cfg export-subst +# For setuptools_scm with .git_archival.txt +.git_archival.txt export-subst +# Line Endings configuration file for Git +# Set the default behavior, in case people don't have or can't have core.autocrlf set. +* text=auto diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ddf2583..f47fb6b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,92 +19,57 @@ jobs: defaults: run: shell: bash -l {0} - env: - CHANS: "-c pyviz -c bokeh" - PKG_TEST_PYTHON: "--test-python=py37" - PYTHON_VERSION: "3.7" - MPLBACKEND: "Agg" - CONDA_UPLOAD_TOKEN: ${{ secrets.CONDA_UPLOAD_TOKEN }} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: "100" - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v4 + - name: Fetch unshallow + run: git fetch --prune --tags --unshallow -f - uses: conda-incubator/setup-miniconda@v2 with: miniconda-version: "latest" - python-version: ${{ env.PYTHON_VERSION }} - - name: Fetch - run: git fetch --prune --tags -f + python-version: "3.11" + auto-update-conda: true - name: Set output id: vars run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - name: conda setup run: | - conda config --set always_yes True - conda install -c pyviz "pyctdev>=0.5" - doit ecosystem_setup + conda install anaconda-client conda-build setuptools_scm - name: conda build - run: doit package_build $CHANS $PKG_TEST_PYTHON --test-group=unit + run: | + VERSION=`python -m setuptools_scm` conda build conda.recipe/ - name: conda dev upload if: (github.event_name == 'push' && (contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc'))) - run: doit package_upload --token=$CONDA_UPLOAD_TOKEN --label=dev + run: | + anaconda --token ${{ secrets.CONDA_UPLOAD_TOKEN }} upload --user pyviz --label=dev $(VERSION=`python -m setuptools_scm` conda build --output conda.recipe) - name: conda main upload if: (github.event_name == 'push' && !(contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc'))) - run: doit package_upload --token=$CONDA_UPLOAD_TOKEN --label=dev --label=main + run: | + anaconda --token ${{ secrets.CONDA_UPLOAD_TOKEN }} upload --user pyviz --label=dev --label=main $(VERSION=`python -m setuptools_scm` conda build --output conda.recipe) pip_build: name: Build PyPI Packages runs-on: 'ubuntu-latest' defaults: run: shell: bash -l {0} - env: - CHANS: "-c pyviz -c bokeh" - PKG_TEST_PYTHON: "--test-python=py37" - PYTHON_VERSION: "3.7" - MPLBACKEND: "Agg" - PPU: ${{ secrets.PPU }} - PPP: ${{ secrets.PPP }} - PYPI: "https://upload.pypi.org/legacy/" steps: - - uses: actions/checkout@v3 - with: - fetch-depth: "100" - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - uses: conda-incubator/setup-miniconda@v2 + - uses: actions/checkout@v4 + - name: Fetch unshallow + run: git fetch --prune --tags --unshallow -f + - name: Set up Python + uses: actions/setup-python@v5 with: - miniconda-version: "latest" - python-version: ${{ env.PYTHON_VERSION }} - - name: Fetch - run: git fetch --prune --tags -f - - name: conda setup - run: | - conda config --set always_yes True - conda install -c pyviz "pyctdev>=0.5" - doit ecosystem_setup - doit env_create $CHANS --python=$PYTHON_VERSION + python-version: "3.11" - name: env setup run: | - eval "$(conda shell.bash hook)" - conda activate test-environment - doit develop_install $CHANS_DEV -o unit_tests - doit pip_on_conda - - name: doit env_capture - run: | - conda activate test-environment - doit env_capture + python -m pip install --upgrade pip + python -m pip install build - name: pip build run: | - eval "$(conda shell.bash hook)" - conda activate test-environment - doit ecosystem=pip package_build - - name: pip upload + python -m build + - name: Publish package to PyPI if: github.event_name == 'push' - run: | - eval "$(conda shell.bash hook)" - conda activate test-environment - doit ecosystem=pip package_upload -u $PPU -p $PPP -r $PYPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: ${{ secrets.PPU }} + password: ${{ secrets.PPP }} + packages-dir: dist/ diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index aa00148..77d5ec8 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -31,42 +31,25 @@ jobs: run: shell: bash -l {0} env: - DESC: "Documentation build" MPLBACKEND: "Agg" DISPLAY: ":99.0" - PYTHON_DOCS_VERSION: "3.9" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: "100" - - uses: conda-incubator/setup-miniconda@v2 - with: - miniconda-version: "latest" - name: Fetch run: git fetch --prune --tags -f - name: Set output id: vars run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: conda setup - # conda-forge is required to install pydata-sphinx-theme and sphinx-copybutton. - run: | - conda create -n test-environment - conda activate test-environment - conda config --env --append channels pyviz/label/dev --append channels conda-forge - conda config --env --remove channels defaults - conda install python=${{ env.PYTHON_DOCS_VERSION}} pyctdev - - name: doit develop_install - run: | - conda activate test-environment - doit develop_install -o doc -o examples - - name: doit env_capture - run: | - conda activate test-environment - doit env_capture + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: install + run: pip install ."[doc, examples]" - name: build docs - run: | - conda activate test-environment - doit build_website + run: sphinx-build -b html doc builtdocs - name: git status run: | git status diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1b42636..cacdfa3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,71 +16,51 @@ concurrency: cancel-in-progress: true jobs: + pre_commit: + name: Run pre-commit + runs-on: 'ubuntu-latest' + steps: + - uses: holoviz-dev/holoviz_tasks/pre-commit@v0.1a19 test_suite: name: Pytest on ${{ matrix.python-version }}, ${{ matrix.os }} + needs: [pre_commit] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] # Run on the full set on schedule, workflow_dispatch and push&tags events, otherwise on a subset. - python-version: ${{ ( github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || ( github.event_name == 'push' && github.ref_type == 'tag' ) ) && fromJSON('["3.7", "3.8", "3.9", "3.10", "3.11"]') || fromJSON('["3.7", "3.11"]') }} + # python-version: ${{ ( github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || ( github.event_name == 'push' && github.ref_type == 'tag' ) ) && fromJSON('["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]') || fromJSON('["3.7", "3.12"]') }} + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] timeout-minutes: 60 defaults: run: - shell: bash -l {0} + shell: bash -l {0} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ENV_NAME: "colorcet" MPLBACKEND: "Agg" DISPLAY: ":99.0" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: "100" - - uses: conda-incubator/setup-miniconda@v2 - with: - miniconda-version: "latest" - name: Fetch run: git fetch --prune --tags -f - - name: conda setup - run: | - conda update -n base conda - conda create -n ${{ env.ENV_NAME }} - conda activate ${{ env.ENV_NAME }} - conda config --env --prepend channels pyviz/label/dev - conda config --env --show-sources - conda install python=${{ matrix.python-version }} pyctdev - - name: doit develop_install - run: | - conda activate ${{ env.ENV_NAME }} - doit develop_install -o tests -o examples - - name: pin bokeh on Python 3.8 - if: matrix.python-version == '3.8' - # Pin to avoid pulling an old version of Bokeh and Panel. - run: | - conda activate ${{ env.ENV_NAME }} - conda install "bokeh >=3" - - name: doit env_capture - run: | - conda activate ${{ env.ENV_NAME }} - doit env_capture - - name: doit test_lint - run: | - conda activate ${{ env.ENV_NAME }} - doit test_lint - - name: doit test_unit - run: | - conda activate ${{ env.ENV_NAME }} - doit test_unit - - name: doit test_examples - run: | - conda activate ${{ env.ENV_NAME }} - doit test_examples + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: pip update + run: pip install --upgrade pip + - name: install + run: pip install -e ."[tests,tests_examples,tests_extra]" + - name: pip list + run: pip list + - name: unit tests + run: pytest colorcet --cov=colorcet --cov-append --cov-report xml + - name: examples tests + run: pytest doc --nbval-lax -p no:python - name: doit test_unit_extra - run: | - conda activate ${{ env.ENV_NAME }} - pip install pytest-mpl - doit test_unit_extra - - uses: codecov/codecov-action@v3 + run: pytest colorcet --mpl + - uses: codecov/codecov-action@v4 if: github.event_name == 'push' diff --git a/.gitignore b/.gitignore index 303ec0a..17b386f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,17 +18,16 @@ coverage.xml __pycache__ .doit* *.egg-info -*/.version pip-wheel-metadata **.ipynb_checkpoints /build +dist/ +.venv/ +venv/ # nbsite # these files normally shouldn't be checked in as they should be # dynamically built from notebooks -doc/**/*.rst -doc/**/*.ipynb -doc/**/*.json # this dir contains the whole website and should not be checked in on main builtdocs/ # myst-nb output dir @@ -36,3 +35,6 @@ jupyter_execute/ # CET_updates.py is an intermediate file and should not be versioned. assets/CET_updates.py + +# setuptools_scm +colorcet/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..633b8d3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +# This is the configuration for pre-commit, a local framework for managing pre-commit hooks +# Check out the docs at: https://pre-commit.com/ + +default_stages: [commit] + +# Exclude Matlab files +exclude: '.+\.m$' + +repos: +- repo: https://github.com/nbQA-dev/nbQA + rev: 1.7.1 + hooks: + - id: nbqa-flake8 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-toml + - id: detect-private-key + - id: end-of-file-fixer + exclude: (\.min\.js$|\.svg$) + - id: trailing-whitespace +- repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 # See 'setup.cfg' for args + args: [colorcet] + files: colorcet/ +- repo: https://github.com/hoxbro/clean_notebook + rev: v0.1.15a2 + hooks: + - id: clean-notebook diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 03c6c31..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,8 +0,0 @@ -include LICENSE.txt -include README.md -include colorcet/.version -graft examples -graft colorcet/examples -global-exclude *.py[co] -global-exclude *~ -global-exclude *.ipynb_checkpoints/* diff --git a/README.md b/README.md index 52d460b..9e1b8be 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ by Peter Kovesi at the Center for Exploration Targeting. ## Installation -Colorcet supports Python 3.7, 3.8, 3.9, 3.10 and 3.11 on Linux, Windows, or Mac +Colorcet supports Python 3.7 and greater on Linux, Windows, or Mac and can be installed with conda: ```sh @@ -41,16 +41,6 @@ or with pip: python -m pip install colorcet ``` -Once installed you can copy the examples into the current directory using the colorcet command and run them using the Jupyter notebook: - -```sh -colorcet examples -cd colorcet-examples -jupyter notebook -``` - -(Here colorcet examples is a shorthand for colorcet copy-examples --path colorcet-examples && colorcet fetch-data --path colorcet-examples.) - To work with JupyterLab you will also need the PyViz JupyterLab extension: ```sh diff --git a/assets/CET_to_py.py b/assets/CET_to_py.py index 867c09b..56a4504 100644 --- a/assets/CET_to_py.py +++ b/assets/CET_to_py.py @@ -55,12 +55,15 @@ Some of the Glasbey sets are aliased to short names as explained in the User Guide. """ -from colorcet.version import Version -__version__ = str(Version(fpath=__file__, archive_commit="$Format:%h$",reponame="datashader")) +import os from collections import OrderedDict from itertools import chain +# Define '__version__' +from importlib.metadata import version +__version__ = version('colorcet') + class AttrODict(OrderedDict): """Ordered dictionary with attribute access (e.g. for tab completion)""" @@ -150,7 +153,7 @@ def all_original_names(group=None, not_group=None, only_aliased=False, only_CET= Returns a list (optionally filtered) of the names of the available colormaps Filters available: - group: only include maps whose name include the given string(s) - (e.g. "'linear'" or "['linear','diverging']"). + (e.g. "'linear'" or "['linear','diverging']"). - not_group: filter out any maps whose names include the given string(s) - only_aliased: only include maps with shorter/simpler aliases - only_CET: only include maps from CET diff --git a/assets/rename_CET_maps.py b/assets/rename_CET_maps.py index c71738c..634ae3c 100644 --- a/assets/rename_CET_maps.py +++ b/assets/rename_CET_maps.py @@ -31,4 +31,3 @@ subprocess.run(f'git mv -vk "{path}" "{new_path}"', shell=True) subprocess.run("git rm -rf CETperceptual_csv_0_1_v2", shell=True) - diff --git a/colorcet/__init__.py b/colorcet/__init__.py index fc808a0..1d03e44 100644 --- a/colorcet/__init__.py +++ b/colorcet/__init__.py @@ -40,12 +40,29 @@ Some of the Glasbey sets are aliased to short names as explained in the User Guide. """ -from .version import Version -__version__ = str(Version(fpath=__file__, archive_commit="$Format:%h$", reponame="colorcet")) - from collections import OrderedDict from itertools import chain +# Define '__version__' +try: + # __version__ was added in _version in setuptools-scm 7.0.0, we rely on + # the hopefully stable version variable. + from ._version import version as __version__ +except (ModuleNotFoundError, ImportError): + # Either _version doesn't exist (ModuleNotFoundError) or version isn't + # in _version (ImportError). ModuleNotFoundError is a subclass of + # ImportError, let's be explicit anyway. + + # Try something else: + from importlib.metadata import version as mversion, PackageNotFoundError + + try: + __version__ = mversion("colorcet") + except PackageNotFoundError: + # The user is probably trying to run this without having installed + # the package. + __version__ = "0.0.0+unknown" + class AttrODict(OrderedDict): """Ordered dictionary with attribute access (e.g. for tab completion)""" @@ -143,7 +160,7 @@ def all_original_names(group=None, not_group=None, only_aliased=False, only_CET= Returns a list (optionally filtered) of the names of the available colormaps Filters available: - group: only include maps whose name include the given string(s) - (e.g. "'linear'" or "['linear','diverging']"). + (e.g. "'linear'" or "['linear','diverging']"). - not_group: filter out any maps whose names include the given string(s) - only_aliased: only include maps with shorter/simpler aliases - only_CET: only include maps from CET diff --git a/colorcet/__main__.py b/colorcet/__main__.py deleted file mode 100644 index 15abc56..0000000 --- a/colorcet/__main__.py +++ /dev/null @@ -1,12 +0,0 @@ -def main(args=None): - try: - import pyct.cmd - except ImportError: - import sys - from . import _missing_cmd - print(_missing_cmd()) - sys.exit(1) - return pyct.cmd.substitute_main('colorcet',args=args) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/colorcet/tests/baseline/test_matplotlib.png b/colorcet/tests/baseline/test_matplotlib.png index c478118d0a46fe1dd9f89dcfe3255b70ccd6a18b..42fe921b4dfdbc202a77e0dafe6d719eb5cc3748 100644 GIT binary patch literal 12330 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 z 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 M-!v*Sxc%UN0Q1nm^8f$< diff --git a/colorcet/tests/baseline/test_matplotlib_glasbey.png b/colorcet/tests/baseline/test_matplotlib_glasbey.png index 284b5977c9842fcf377e3ef7138a96bb10d1f5e4..478e0be2fc0b8a1633b3290205b73653cd44900b 100644 GIT binary patch literal 11693 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 Vt#tAOkPbl5V?o~&zy0CH{{ZH3feruw literal 11774 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 SA~>`RIeXd}QT^-hfBZM*rki~L diff --git a/colorcet/tests/test_bokeh.py b/colorcet/tests/test_bokeh.py index 7af2e4e..1342f39 100644 --- a/colorcet/tests/test_bokeh.py +++ b/colorcet/tests/test_bokeh.py @@ -16,4 +16,4 @@ def test_bokeh_palette_glasbey_do_not_start_with_bw(): cmap = cc.palette[name] assert isinstance(cmap, list) assert len(cmap) == 256 - assert {cmap[0], cmap[1]} != {'#00000', '#ffffff'} \ No newline at end of file + assert {cmap[0], cmap[1]} != {'#00000', '#ffffff'} diff --git a/colorcet/version.py b/colorcet/version.py deleted file mode 100644 index ae00fc4..0000000 --- a/colorcet/version.py +++ /dev/null @@ -1,771 +0,0 @@ -""" -Provide consistent and up-to-date ``__version__`` strings for -Python packages. - -See https://github.com/holoviz/autover for more information. -""" - -# The Version class is a copy of autover.version.Version v0.2.5, -# except as noted below. -# -# The current version of autover supports a workflow based on tagging -# a git repository, and reports PEP440 compliant version information. -# Previously, the workflow required editing of version numbers in -# source code, and the version was not necessarily PEP440 compliant. -# Version.__new__ is added here to provide the previous Version class -# (OldDeprecatedVersion) if Version is called in the old way. - - -__author__ = 'Jean-Luc Stevens' - -import os, subprocess, json - -def run_cmd(args, cwd=None): - proc = subprocess.Popen(args, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=cwd) - output, error = (str(s.decode()).strip() for s in proc.communicate()) - - # Detects errors as _either_ a non-zero return code _or_ messages - # printed to stderr, because the return code is erroneously fixed at - # zero in some cases (see https://github.com/holoviz/param/pull/389). - if proc.returncode != 0 or len(error) > 0: - raise Exception(proc.returncode, error) - return output - - - -class Version(object): - """ - A simple approach to Python package versioning that supports PyPI - releases and additional information when working with version - control. When obtaining a package from PyPI, the version returned - is a string-formatted rendering of the supplied release tuple. - For instance, release (1,0) tagged as ``v1.0`` in the version - control system will return ``1.0`` for ``str(__version__)``. Any - number of items can be supplied in the release tuple, with either - two or three numeric versioning levels typical. - - During development, a command like ``git describe`` will be used to - compute the number of commits since the last version tag, the short - commit hash, and whether the commit is dirty (has changes not yet - committed). Version tags must start with a lowercase 'v' and have a - period in them, e.g. v2.0, v0.9.8 or v0.1 and may include the PEP440 - prerelease identifiers of 'a' (alpha) 'b' (beta) or 'rc' (release - candidate) allowing tags such as v2.0.a3, v0.9.8.b3 or v0.1.rc5. - - Also note that when version control system (VCS) information is - used, the number of commits since the last version tag is - determined. This approach is often useful in practice to decide - which version is newer for a single developer, but will not - necessarily be reliable when comparing against a different fork or - branch in a distributed VCS. - - For git, if you want version control information available even in - an exported archive (e.g. a .zip file from GitHub), you can set - the following line in the .gitattributes file of your project:: - - __init__.py export-subst - - Note that to support pip installation directly from GitHub via git - archive, a .version file must be tracked by the repo to supply the - release number (otherwise only the short SHA is available). - - The PEP440 format returned is [N!]N(.N)*[{a|b|rc}N][.postN+SHA] - where everything before .postN is obtained from the tag, the N in - .postN is the number of commits since the last tag and the SHA is - obtained via git describe. This later portion is only shown if the - commit count since the last tag is non zero. Instead of '.post', an - alternate valid prefix such as '.rev', '_rev', '_r' or '.r' may be - supplied.""" - - def __new__(cls,**kw): - # If called in the old way, provide the previous class. Means - # PEP440/tag based workflow warning below will never appear. - if ('release' in kw and kw['release'] is not None) or \ - ('dev' in kw and kw['dev'] is not None) or \ - ('commit_count' in kw): - return OldDeprecatedVersion(**kw) - else: - return super(Version, cls).__new__(cls) - - - def __init__(self, release=None, fpath=None, commit=None, reponame=None, - commit_count_prefix='.post', archive_commit=None, **kwargs): - """ - :release: Release tuple (corresponding to the current VCS tag) - :commit Short SHA. Set to '$Format:%h$' for git archive support. - :fpath: Set to ``__file__`` to access version control information - :reponame: Used to verify VCS repository name. - """ - self.fpath = fpath - self._expected_commit = commit - - if release is not None or 'commit_count' in kwargs: - print('WARNING: param.Version now supports PEP440 and a new tag based workflow. See param/version.py for more details') - - self.expected_release = release - - self._commit = None if (commit is None or commit.startswith("$Format")) else commit - self._commit_count = None - self._release = None - self._dirty = False - self._prerelease = None - - self.archive_commit= archive_commit - - self.reponame = reponame - self.commit_count_prefix = commit_count_prefix - - @property - def prerelease(self): - """ - Either None or one of 'aN' (alpha), 'bN' (beta) or 'rcN' - (release candidate) where N is an integer. - """ - return self.fetch()._prerelease - - @property - def release(self): - "Return the release tuple" - return self.fetch()._release - - @property - def commit(self): - "A specification for this particular VCS version, e.g. a short git SHA" - return self.fetch()._commit - - @property - def commit_count(self): - "Return the number of commits since the last release" - return self.fetch()._commit_count - - @property - def dirty(self): - "True if there are uncommitted changes, False otherwise" - return self.fetch()._dirty - - - def fetch(self): - """ - Returns a tuple of the major version together with the - appropriate SHA and dirty bit (for development version only). - """ - if self._release is not None: - return self - - self._release = self.expected_release - if not self.fpath: - self._commit = self._expected_commit - return self - - # Only git right now but easily extended to SVN, Mercurial, etc. - for cmd in ['git', 'git.cmd', 'git.exe']: - try: - self.git_fetch(cmd) - break - except EnvironmentError: - pass - return self - - - def git_fetch(self, cmd='git', as_string=False): - commit_argument = self._commit - output = None - try: - if self.reponame is not None: - # Verify this is the correct repository (since fpath could - # be an unrelated git repository, and autover could just have - # been copied/installed into it). - remotes = run_cmd([cmd, 'remote', '-v'], - cwd=os.path.dirname(self.fpath)) - repo_matches = ['/' + self.reponame + '.git' , - # A remote 'server:reponame.git' can also be referred - # to (i.e. cloned) as `server:reponame`. - '/' + self.reponame + ' '] - if not any(m in remotes for m in repo_matches): - try: - output = self._output_from_file() - if output is not None: - self._update_from_vcs(output) - except: pass - if output is None: - # glob pattern (not regexp) matching vX.Y.Z* tags - output = run_cmd([cmd, 'describe', '--long', '--match', - "v[0-9]*.[0-9]*.[0-9]*", '--dirty'], - cwd=os.path.dirname(self.fpath)) - if as_string: return output - except Exception as e1: - try: - output = self._output_from_file() - if output is not None: - self._update_from_vcs(output) - if self._known_stale(): - self._commit_count = None - if as_string: return output - - # If an explicit commit was supplied (e.g from git - # archive), it should take precedence over the file. - if commit_argument: - self._commit = commit_argument - return - - except IOError: - if e1.args[1] == 'fatal: No names found, cannot describe anything.': - raise Exception("Cannot find any git version tags of format v*.*") - # If there is any other error, return (release value still useful) - return self - - self._update_from_vcs(output) - - - def _known_stale(self): - """ - The commit is known to be from a file (and therefore stale) if a - SHA is supplied by git archive and doesn't match the parsed commit. - """ - if self._output_from_file() is None: - commit = None - else: - commit = self.commit - - known_stale = (self.archive_commit is not None - and not self.archive_commit.startswith('$Format') - and self.archive_commit != commit) - if known_stale: self._commit_count = None - return known_stale - - def _output_from_file(self, entry='git_describe'): - """ - Read the version from a .version file that may exist alongside __init__.py. - - This file can be generated by piping the following output to file: - - git describe --long --match v*.* - """ - try: - vfile = os.path.join(os.path.dirname(self.fpath), '.version') - with open(vfile, 'r') as f: - return json.loads(f.read()).get(entry, None) - except: # File may be missing if using pip + git archive - return None - - - def _update_from_vcs(self, output): - "Update state based on the VCS state e.g the output of git describe" - split = output[1:].split('-') - dot_split = split[0].split('.') - for prefix in ['a','b','rc']: - if prefix in dot_split[-1]: - prefix_split = dot_split[-1].split(prefix) - self._prerelease = prefix + prefix_split[-1] - dot_split[-1] = prefix_split[0] - - - self._release = tuple(int(el) for el in dot_split) - self._commit_count = int(split[1]) - - self._commit = str(split[2][1:]) # Strip out 'g' prefix ('g'=>'git') - - self._dirty = (split[-1]=='dirty') - return self - - def __str__(self): - """ - Version in x.y.z string format. Does not include the "v" - prefix of the VCS version tags, for pip compatibility. - - If the commit count is non-zero or the repository is dirty, - the string representation is equivalent to the output of:: - - git describe --long --match v*.* --dirty - - (with "v" prefix removed). - """ - known_stale = self._known_stale() - if self.release is None and not known_stale: - extracted_directory_tag = self._output_from_file(entry='extracted_directory_tag') - return 'None' if extracted_directory_tag is None else extracted_directory_tag - elif self.release is None and known_stale: - extracted_directory_tag = self._output_from_file(entry='extracted_directory_tag') - if extracted_directory_tag is not None: - return extracted_directory_tag - return '0.0.0+g{SHA}-gitarchive'.format(SHA=self.archive_commit) - - release = '.'.join(str(el) for el in self.release) - prerelease = '' if self.prerelease is None else self.prerelease - - if self.commit_count == 0 and not self.dirty: - return release + prerelease - - commit = self.commit - dirty = '-dirty' if self.dirty else '' - archive_commit = '' - if known_stale: - archive_commit = '-gitarchive' - commit = self.archive_commit - - if archive_commit != '': - postcount = self.commit_count_prefix + '0' - elif self.commit_count not in [0, None]: - postcount = self.commit_count_prefix + str(self.commit_count) - else: - postcount = '' - - components = [release, prerelease, postcount, - '' if commit is None else '+g' + commit, dirty, - archive_commit] - return ''.join(components) - - def __repr__(self): - return str(self) - - def abbrev(self): - """ - Abbreviated string representation of just the release number. - """ - return '.'.join(str(el) for el in self.release) - - def verify(self, string_version=None): - """ - Check that the version information is consistent with the VCS - before doing a release. If supplied with a string version, - this is also checked against the current version. Should be - called from setup.py with the declared package version before - releasing to PyPI. - """ - if string_version and string_version != str(self): - raise Exception("Supplied string version does not match current version.") - - if self.dirty: - raise Exception("Current working directory is dirty.") - - if self.expected_release is not None and self.release != self.expected_release: - raise Exception("Declared release does not match current release tag.") - - if self.commit_count !=0: - raise Exception("Please update the VCS version tag before release.") - - if (self._expected_commit is not None - and not self._expected_commit.startswith( "$Format")): - raise Exception("Declared release does not match the VCS version tag") - - - - @classmethod - def get_setup_version(cls, setup_path, reponame, describe=False, - dirty='report', pkgname=None, archive_commit=None): - """ - Helper for use in setup.py to get the version from the .version file (if available) - or more up-to-date information from git describe (if available). - - Assumes the __init__.py will be found in the directory - {reponame}/__init__.py relative to setup.py unless pkgname is - explicitly specified in which case that name is used instead. - - If describe is True, the raw string obtained from git described is - returned which is useful for updating the .version file. - - The dirty policy can be one of 'report', 'strip', 'raise'. If it is - 'report' the version string may end in '-dirty' if the repository is - in a dirty state. If the policy is 'strip', the '-dirty' suffix - will be stripped out if present. If the policy is 'raise', an - exception is raised if the repository is in a dirty state. This can - be useful if you want to make sure packages are not built from a - dirty repository state. - """ - pkgname = reponame if pkgname is None else pkgname - policies = ['raise','report', 'strip'] - if dirty not in policies: - raise AssertionError("get_setup_version dirty policy must be in %r" % policies) - - fpath = os.path.join(setup_path, pkgname, "__init__.py") - version = Version(fpath=fpath, reponame=reponame, archive_commit=archive_commit) - if describe: - vstring = version.git_fetch(as_string=True) - else: - vstring = str(version) - - if version.dirty and dirty == 'raise': - raise AssertionError('Repository is in a dirty state.') - elif version.dirty and dirty=='strip': - return vstring.replace('-dirty', '') - else: - return vstring - - - @classmethod - def extract_directory_tag(cls, setup_path, reponame): - setup_dir = os.path.split(setup_path)[-1] # Directory containing setup.py - prefix = reponame + '-' # Prefix to match - if setup_dir.startswith(prefix): - tag = setup_dir[len(prefix):] - # Assuming the tag is a version if it isn't empty, 'master' and has a dot in it - if tag not in ['', 'master'] and ('.' in tag): - return tag - return None - - - @classmethod - def setup_version(cls, setup_path, reponame, archive_commit=None, - pkgname=None, dirty='report'): - info = {} - git_describe = None - pkgname = reponame if pkgname is None else pkgname - try: - # Will only work if in a git repo and git is available - git_describe = Version.get_setup_version(setup_path, - reponame, - describe=True, - dirty=dirty, - pkgname=pkgname, - archive_commit=archive_commit) - - if git_describe is not None: - info['git_describe'] = git_describe - except: pass - - if git_describe is None: - extracted_directory_tag = Version.extract_directory_tag(setup_path, reponame) - if extracted_directory_tag is not None: - info['extracted_directory_tag'] = extracted_directory_tag - try: - with open(os.path.join(setup_path, pkgname, '.version'), 'w') as f: - f.write(json.dumps({'extracted_directory_tag':extracted_directory_tag})) - except: - print('Error in setup_version: could not write .version file.') - - - info['version_string'] = Version.get_setup_version(setup_path, - reponame, - describe=False, - dirty=dirty, - pkgname=pkgname, - archive_commit=archive_commit) - try: - with open(os.path.join(setup_path, pkgname, '.version'), 'w') as f: - f.write(json.dumps(info)) - except: - print('Error in setup_version: could not write .version file.') - - return info['version_string'] - - - -def get_setup_version(location, reponame, pkgname=None, archive_commit=None): - """Helper for use in setup.py to get the current version from either - git describe or the .version file (if available). - - Set pkgname to the package name if it is different from the - repository name. - - To ensure git information is included in a git archive, add - setup.py to .gitattributes (in addition to __init__): - ``` - __init__.py export-subst - setup.py export-subst - ``` - Then supply "$Format:%h$" for archive_commit. - - """ - import warnings - pkgname = reponame if pkgname is None else pkgname - if archive_commit is None: - warnings.warn("No archive commit available; git archives will not contain version information") - return Version.setup_version(os.path.dirname(os.path.abspath(location)),reponame,pkgname=pkgname,archive_commit=archive_commit) - - -def get_setupcfg_version(): - """As get_setup_version(), but configure via setup.cfg. - - If your project uses setup.cfg to configure setuptools, and hence has - at least a "name" key in the [metadata] section, you can - set the version as follows: - ``` - [metadata] - name = mypackage - version = attr: autover.version.get_setup_version2 - ``` - - If the repository name is different from the package name, specify - `reponame` as a [tool:autover] option: - ``` - [tool:autover] - reponame = mypackage - ``` - - To ensure git information is included in a git archive, add - setup.cfg to .gitattributes (in addition to __init__): - ``` - __init__.py export-subst - setup.cfg export-subst - ``` - - Then add the following to setup.cfg: - ``` - [tool:autover.configparser_workaround.archive_commit=$Format:%h$] - ``` - - The above being a section heading rather than just a key is - because setuptools requires % to be escaped with %, or it can't - parse setup.cfg...but then git export-subst would not work. - - """ - try: - import configparser - except ImportError: - import ConfigParser as configparser # python2 (also prevents dict-like access) - import re - cfg = "setup.cfg" - autover_section = 'tool:autover' - config = configparser.ConfigParser() - config.read(cfg) - pkgname = config.get('metadata','name') - reponame = config.get(autover_section,'reponame',vars={'reponame':pkgname}) if autover_section in config.sections() else pkgname - - ### - # hack archive_commit into section heading; see docstring - archive_commit = None - archive_commit_key = autover_section+'.configparser_workaround.archive_commit' - for section in config.sections(): - if section.startswith(archive_commit_key): - archive_commit = re.match(r".*=\s*(\S*)\s*",section).group(1) - ### - return get_setup_version(cfg,reponame=reponame,pkgname=pkgname,archive_commit=archive_commit) - - -# from param/version.py aa087db29976d9b7e0f59c29789dfd721c85afd0 -class OldDeprecatedVersion(object): - """ - A simple approach to Python package versioning that supports PyPI - releases and additional information when working with version - control. When obtaining a package from PyPI, the version returned - is a string-formatted rendering of the supplied release tuple. - For instance, release (1,0) tagged as ``v1.0`` in the version - control system will return ``1.0`` for ``str(__version__)``. Any - number of items can be supplied in the release tuple, with either - two or three numeric versioning levels typical. - - During development, a command like ``git describe`` will be used to - compute the number of commits since the last version tag, the - short commit hash, and whether the commit is dirty (has changes - not yet committed). Version tags must start with a lowercase 'v' - and have a period in them, e.g. v2.0, v0.9.8 or v0.1. - - Development versions are supported by setting the dev argument to an - appropriate dev version number. The corresponding tag can be PEP440 - compliant (using .devX) of the form v0.1.dev3, v1.9.0.dev2 etc but - it doesn't have to be as the dot may be omitted i.e v0.1dev3, - v1.9.0dev2 etc. - - Also note that when version control system (VCS) information is - used, the comparison operators take into account the number of - commits since the last version tag. This approach is often useful - in practice to decide which version is newer for a single - developer, but will not necessarily be reliable when comparing - against a different fork or branch in a distributed VCS. - - For git, if you want version control information available even in - an exported archive (e.g. a .zip file from GitHub), you can set - the following line in the .gitattributes file of your project:: - - __init__.py export-subst - """ - - def __init__(self, release=None, fpath=None, commit=None, - reponame=None, dev=None, commit_count=0): - """ - :release: Release tuple (corresponding to the current VCS tag) - :commit Short SHA. Set to '$Format:%h$' for git archive support. - :fpath: Set to ``__file__`` to access version control information - :reponame: Used to verify VCS repository name. - :dev: Development version number. None if not a development version. - :commit_count Commits since last release. Set for dev releases. - """ - self.fpath = fpath - self._expected_commit = commit - self.expected_release = release - - self._commit = None if commit in [None, "$Format:%h$"] else commit - self._commit_count = commit_count - self._release = None - self._dirty = False - self.reponame = reponame - self.dev = dev - - @property - def release(self): - "Return the release tuple" - return self.fetch()._release - - @property - def commit(self): - "A specification for this particular VCS version, e.g. a short git SHA" - return self.fetch()._commit - - @property - def commit_count(self): - "Return the number of commits since the last release" - return self.fetch()._commit_count - - @property - def dirty(self): - "True if there are uncommitted changes, False otherwise" - return self.fetch()._dirty - - - def fetch(self): - """ - Returns a tuple of the major version together with the - appropriate SHA and dirty bit (for development version only). - """ - if self._release is not None: - return self - - self._release = self.expected_release - if not self.fpath: - self._commit = self._expected_commit - return self - - # Only git right now but easily extended to SVN, Mercurial, etc. - for cmd in ['git', 'git.cmd', 'git.exe']: - try: - self.git_fetch(cmd) - break - except EnvironmentError: - pass - return self - - - def git_fetch(self, cmd='git'): - try: - if self.reponame is not None: - # Verify this is the correct repository (since fpath could - # be an unrelated git repository, and param could just have - # been copied/installed into it). - output = run_cmd([cmd, 'remote', '-v'], - cwd=os.path.dirname(self.fpath)) - repo_matches = ['/' + self.reponame + '.git' , - # A remote 'server:reponame.git' can also be referred - # to (i.e. cloned) as `server:reponame`. - '/' + self.reponame + ' '] - if not any(m in output for m in repo_matches): - return self - - output = run_cmd([cmd, 'describe', '--long', '--match', 'v*.*', '--dirty'], - cwd=os.path.dirname(self.fpath)) - except Exception as e: - if e.args[1] == 'fatal: No names found, cannot describe anything.': - raise Exception("Cannot find any git version tags of format v*.*") - # If there is any other error, return (release value still useful) - return self - - self._update_from_vcs(output) - - def _update_from_vcs(self, output): - "Update state based on the VCS state e.g the output of git describe" - split = output[1:].split('-') - if 'dev' in split[0]: - dev_split = split[0].split('dev') - self.dev = int(dev_split[1]) - split[0] = dev_split[0] - # Remove the pep440 dot if present - if split[0].endswith('.'): - split[0] = dev_split[0][:-1] - - self._release = tuple(int(el) for el in split[0].split('.')) - self._commit_count = int(split[1]) - self._commit = str(split[2][1:]) # Strip out 'g' prefix ('g'=>'git') - self._dirty = (split[-1]=='dirty') - return self - - - def __str__(self): - """ - Version in x.y.z string format. Does not include the "v" - prefix of the VCS version tags, for pip compatibility. - - If the commit count is non-zero or the repository is dirty, - the string representation is equivalent to the output of:: - - git describe --long --match v*.* --dirty - - (with "v" prefix removed). - """ - if self.release is None: return 'None' - release = '.'.join(str(el) for el in self.release) - release = '%s.dev%d' % (release, self.dev) if self.dev is not None else release - - if (self._expected_commit is not None) and ("$Format" not in self._expected_commit): - pass # Concrete commit supplied - print full version string - elif (self.commit_count == 0 and not self.dirty): - return release - - dirty_status = '-dirty' if self.dirty else '' - return '%s-%s-g%s%s' % (release, self.commit_count if self.commit_count else 'x', - self.commit, dirty_status) - - def __repr__(self): - return str(self) - - def abbrev(self,dev_suffix=""): - """ - Abbreviated string representation, optionally declaring whether it is - a development version. - """ - return '.'.join(str(el) for el in self.release) + \ - (dev_suffix if self.commit_count > 0 or self.dirty else "") - - - def __eq__(self, other): - """ - Two versions are considered equivalent if and only if they are - from the same release, with the same commit count, and are not - dirty. Any dirty version is considered different from any - other version, since it could potentially have any arbitrary - changes even for the same release and commit count. - """ - if self.dirty or other.dirty: return False - return ((self.release, self.commit_count, self.dev) - == (other.release, other.commit_count, other.dev)) - - def __gt__(self, other): - if self.release == other.release: - if self.dev == other.dev: - return self.commit_count > other.commit_count - elif None in [self.dev, other.dev]: - return self.dev is None - else: - return self.dev > other.dev - else: - return (self.release, self.commit_count) > (other.release, other.commit_count) - - def __lt__(self, other): - if self==other: - return False - else: - return not (self > other) - - - def verify(self, string_version=None): - """ - Check that the version information is consistent with the VCS - before doing a release. If supplied with a string version, - this is also checked against the current version. Should be - called from setup.py with the declared package version before - releasing to PyPI. - """ - if string_version and string_version != str(self): - raise Exception("Supplied string version does not match current version.") - - if self.dirty: - raise Exception("Current working directory is dirty.") - - if self.release != self.expected_release: - raise Exception("Declared release does not match current release tag.") - - if self.commit_count !=0: - raise Exception("Please update the VCS version tag before release.") - - if self._expected_commit not in [None, "$Format:%h$"]: - raise Exception("Declared release does not match the VCS version tag") diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 24f2d68..394ade2 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,43 +1,47 @@ -{% set sdata = load_setup_py_data() %} +{% set pyproject = load_file_data('../pyproject.toml', from_recipe_dir=True) %} +{% set buildsystem = pyproject['build-system'] %} +{% set project = pyproject['project'] %} + +{% set name = project['name'] %} +{% set version = VERSION %} package: - name: colorcet - version: {{ sdata['version'] }} + name: {{ name|lower }} + version: {{ version }} source: path: .. build: noarch: python - script: python setup.py install --single-version-externally-managed --record=record.txt - entry_points: - {% for group,epoints in sdata.get("entry_points",{}).items() %} - {% for entry_point in epoints %} - - {{ entry_point }} - {% endfor %} - {% endfor %} + script: {{ PYTHON }} -m pip install . -vv requirements: build: - - python {{ sdata['python_requires'] }} - {% for dep in sdata['extras_require']['build'] %} + - python {{ project['requires-python'] }} + - pip + {% for dep in buildsystem['requires'] %} - {{ dep }} {% endfor %} run: - - python {{ sdata['python_requires'] }} - {% for dep in sdata.get('install_requires',{}) %} - - {{ dep }} - {% endfor %} + - python {{ project['requires-python'] }} test: - imports: - - colorcet requires: - {% for dep in sdata['extras_require']['tests'] %} - - "{{ dep }}" + {% for dep in project['optional-dependencies']['tests'] %} + - {{ dep }} {% endfor %} + source_files: + - pyproject.toml + - colorcet + imports: + - colorcet + commands: + - python -c "import colorcet; ver = colorcet.__version__; assert ver != '0.0.0' and ver != 'unknown'" + - pytest colorcet/ about: - home: {{ sdata['url'] }} - summary: {{ sdata['description'] }} - license: {{ sdata['license'] }} + home: {{ project['urls']['Homepage'] }} + summary: {{ project['description'] }} + license: {{ project['license']['text'] }} + license_file: LICENSE.txt diff --git a/examples/assets/images/census_fire.png b/doc/assets/images/census_fire.png similarity index 100% rename from examples/assets/images/census_fire.png rename to doc/assets/images/census_fire.png diff --git a/examples/assets/images/census_hot.png b/doc/assets/images/census_hot.png similarity index 100% rename from examples/assets/images/census_hot.png rename to doc/assets/images/census_hot.png diff --git a/examples/assets/images/fire.png b/doc/assets/images/fire.png similarity index 100% rename from examples/assets/images/fire.png rename to doc/assets/images/fire.png diff --git a/examples/assets/images/hot.png b/doc/assets/images/hot.png similarity index 100% rename from examples/assets/images/hot.png rename to doc/assets/images/hot.png diff --git a/examples/assets/images/jet.png b/doc/assets/images/jet.png similarity index 100% rename from examples/assets/images/jet.png rename to doc/assets/images/jet.png diff --git a/examples/assets/images/named.png b/doc/assets/images/named.png similarity index 100% rename from examples/assets/images/named.png rename to doc/assets/images/named.png diff --git a/examples/assets/images/rainbow.png b/doc/assets/images/rainbow.png similarity index 100% rename from examples/assets/images/rainbow.png rename to doc/assets/images/rainbow.png diff --git a/examples/assets/images/rainbow4.png b/doc/assets/images/rainbow4.png similarity index 100% rename from examples/assets/images/rainbow4.png rename to doc/assets/images/rainbow4.png diff --git a/examples/assets/write_named.py b/doc/assets/write_named.py similarity index 100% rename from examples/assets/write_named.py rename to doc/assets/write_named.py diff --git a/doc/conf.py b/doc/conf.py index 2b76a2e..6285e81 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,15 +12,12 @@ nbbuild_cell_timeout = 10000 +exclude_patterns = ['governance'] html_static_path += ['_static'] html_theme = 'pydata_sphinx_theme' - -templates_path = ['_templates'] - html_logo = "_static/logo_horizontal.png" html_favicon = "_static/favicon.ico" -html_css_files = [ - 'nbsite.css', +html_css_files += [ 'custom.css' ] diff --git a/doc/getting_started/index.rst b/doc/getting_started/index.rst index c316a4f..86c0c8e 100644 --- a/doc/getting_started/index.rst +++ b/doc/getting_started/index.rst @@ -5,7 +5,7 @@ Getting started Installation ------------ -Colorcet supports Python 2.7, 3.6, 3.7, 3.8 and 3.9 on Linux, Windows, or Mac +Colorcet supports Python 3.7 and greater on Linux, Windows, or Mac and can be installed with conda:: conda install colorcet @@ -57,18 +57,20 @@ or discuss on Gitter. Developer Instructions ---------------------- -1. Install Python 3 `miniconda `_ or `anaconda `_, if you don't already have it on your system. +1. Install Python and pip. 2. Clone the colorcet git repository if you do not already have it:: git clone git://github.com/pyviz/colorcet.git -3. Set up a new conda environment with optional plotting tools:: +3. Set up a new environment with all the required dependencies:: cd colorcet - conda env create -n colorcet matplotlib bokeh holoviews - conda activate colorcet + # + pip install -e .[all] -4. Put the colorcet directory into the Python path in this environment:: +4. Run the unit tests / run the examples tests / build the docs :: - pip install -e . + pytest colorcet + pytest doc --nbval-lax -p no:python + sphinx-build -b html doc builtdocs diff --git a/doc/governance/project-docs/CONTRIBUTING.md b/doc/governance/project-docs/CONTRIBUTING.md index fdfa21f..19a23ab 100644 --- a/doc/governance/project-docs/CONTRIBUTING.md +++ b/doc/governance/project-docs/CONTRIBUTING.md @@ -1,5 +1,5 @@ # Contributing - + For the contributing policy, see [HoloViz/HoloViz - CONTRIBUTING.md](https://github.com/holoviz/holoviz/blob/colorcet-gov/doc/governance/project-docs/CONTRIBUTING.md). - + The Colorcet Project’s equivalently named documents take precedence over any external materials referenced within this linked document above. diff --git a/doc/governance/project-docs/LICENSE.md b/doc/governance/project-docs/LICENSE.md index 2a6c34a..ec6d16e 100644 --- a/doc/governance/project-docs/LICENSE.md +++ b/doc/governance/project-docs/LICENSE.md @@ -1,3 +1,3 @@ # License - + For the license, see [HoloViz/Colorcet - LICENSE.txt](https://github.com/holoviz/colorcet/blob/main/LICENSE.txt). diff --git a/examples/index.ipynb b/doc/index.ipynb similarity index 95% rename from examples/index.ipynb rename to doc/index.ipynb index 44c8768..b203b65 100644 --- a/examples/index.ipynb +++ b/doc/index.ipynb @@ -117,10 +117,25 @@ "\n", "## Samples\n", "\n", - "\n", + "![named](assets/images/named.png)\n", "\n", "The complete set of 100+ maps is shown in the [User Guide](./user_guide/index.ipynb)." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{toctree}\n", + ":hidden:\n", + ":maxdepth: 2\n", + "\n", + "Introduction \n", + "Getting Started \n", + "User Guide \n", + "About \n", + "```" + ] } ], "metadata": { diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index 81c8c06..0000000 --- a/doc/index.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. - Originally generated by nbsite (0.6.0a1): - /Users/jsignell/conda/envs/colorcet/bin/nbsite generate-rst --org pyviz --project-name colorcet --offset 0 - Will not subsequently be overwritten by nbsite, so can be edited. - -.. raw:: html - -

- -.. notebook:: colorcet ../examples/index.ipynb - :offset: 0 - :disable_interactivity_warning: - -.. toctree:: - :hidden: - :maxdepth: 2 - - Introduction - Getting Started - User Guide - About - - -------- - -`Right click to download this notebook from GitHub. `_ diff --git a/examples/user_guide/Categorical.ipynb b/doc/user_guide/Categorical.ipynb similarity index 99% rename from examples/user_guide/Categorical.ipynb rename to doc/user_guide/Categorical.ipynb index fcbf251..8e2ff43 100644 --- a/examples/user_guide/Categorical.ipynb +++ b/doc/user_guide/Categorical.ipynb @@ -4,6 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Categorical\n", + "\n", "## Glasbey colormaps for categorical data\n", "\n", "Colorcet primarily includes continuous colormaps, where each color is meant to be equally spaced in perceptual color space from the preceding and following colors. The resulting colors then convey numerical magnitude to the viewer. Categorical data can also be represented as numbers, but each number is then distinct, with the numerical value important only to distinguish from other values. When categorical data is plotted as colors, each category should have a color visibly distinct from all the other colors, _not_ nearby in color space, to make each category separately visible. \n", diff --git a/doc/user_guide/Categorical.rst b/doc/user_guide/Categorical.rst deleted file mode 100644 index 26c2178..0000000 --- a/doc/user_guide/Categorical.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. - Originally generated by nbsite (0.4.6): - /Users/jbednar/miniconda3/envs/holoviz/bin/nbsite generate-rst --org holoviz --project-name colorcet --offset 0 - Will not subsequently be overwritten by nbsite, so can be edited. - -*********** -Categorical -*********** - -.. notebook:: colorcet ../../examples/user_guide/Categorical.ipynb - :offset: 0 - :disable_interactivity_warning: - -------- - -`Right click to download this notebook from GitHub. `_ diff --git a/examples/user_guide/Continuous.ipynb b/doc/user_guide/Continuous.ipynb similarity index 99% rename from examples/user_guide/Continuous.ipynb rename to doc/user_guide/Continuous.ipynb index 5dc5015..4386c71 100644 --- a/examples/user_guide/Continuous.ipynb +++ b/doc/user_guide/Continuous.ipynb @@ -4,6 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Continuous\n", + "\n", "## Perceptually uniform continuous colormaps from CET\n", "\n", "Peter Kovesi at the Center for Exploration Targeting created a very useful set of [perceptually uniform continuous colormaps](https://arxiv.org/abs/1509.03700), many of which can replace the highly non-uniform colormaps provided with Python plotting programs. Here we will show how to use them via a Python package named [colorcet](https://github.com/holoviz/colorcet), listing all the ones available and allowing you to evaluate how perceptually uniform they are for you, your particular monitor, etc. Download and installation instructions are at the [github site](https://github.com/holoviz/colorcet).\n", diff --git a/doc/user_guide/Continuous.rst b/doc/user_guide/Continuous.rst deleted file mode 100644 index efc2570..0000000 --- a/doc/user_guide/Continuous.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. - Originally generated by nbsite (0.4.6): - /Users/jbednar/miniconda3/envs/holoviz/bin/nbsite generate-rst --org holoviz --project-name colorcet --offset 0 - Will not subsequently be overwritten by nbsite, so can be edited. - -********** -Continuous -********** - -.. notebook:: colorcet ../../examples/user_guide/Continuous.ipynb - :offset: 0 - :disable_interactivity_warning: - - -------- - -`Right click to download this notebook from GitHub. `_ diff --git a/examples/user_guide/index.ipynb b/doc/user_guide/index.ipynb similarity index 95% rename from examples/user_guide/index.ipynb rename to doc/user_guide/index.ipynb index 6beebe0..cfd0944 100644 --- a/examples/user_guide/index.ipynb +++ b/doc/user_guide/index.ipynb @@ -4,6 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# User Guide\n", + "\n", "## Accessing the colormaps\n", "\n", "After importing `colorcet` as `cc`, all the colormaps shown in this notebook will be available for use in different forms. It's a bit difficult to describe, but the idea is that colorcet should have at least one such form convenient for any particular application. There are three distinct versions for each colormap, each of which consists of 256 distinct colors: \n", @@ -141,6 +143,21 @@ "source": [ "For more explanation of the various options see [categorical](Categorical.ipynb) and [continuous](Continuous.ipynb)." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{toctree}\n", + ":titlesonly:\n", + ":hidden:\n", + ":maxdepth: 2\n", + "\n", + "Accessing the colormaps \n", + "Continuous \n", + "Categorical \n", + "```" + ] } ], "metadata": { diff --git a/doc/user_guide/index.rst b/doc/user_guide/index.rst deleted file mode 100644 index faba56d..0000000 --- a/doc/user_guide/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. - Originally generated by nbsite (0.6.1a4): - /Users/jsignell/conda/envs/colorcet/bin/nbsite generate-rst --org pyviz --project-name colorcet --offset 0 - Will not subsequently be overwritten by nbsite, so can be edited. - 2019-04-03 jsignell: reordered categorical and continuous to not be alphabetical - -********** -User Guide -********** - -.. notebook:: colorcet ../../examples/user_guide/index.ipynb - :offset: 0 - :disable_interactivity_warning: - -.. toctree:: - :titlesonly: - :maxdepth: 2 - - Continuous - Categorical - - -------- - -`Right click to download this notebook from GitHub. `_ diff --git a/dodo.py b/dodo.py deleted file mode 100644 index e254b4d..0000000 --- a/dodo.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -This file provides a mechanism to map between the semantic commands that any -project has (build docs, run tests, copy examples ... ) and the specific -command that should be run. - -Most of these commands are stored in pyctdev which is essentially a collection -of the holoviz way of doing these actions. Commands that are newer, or specific -to a particular project, will live in this file instead. To see a list of -all the available commands - after installing pyctdev - run: - -$ doit list - -To run one command, for instance building the website, run: - -$ doit build_website -""" - -import os -if "PYCTDEV_ECOSYSTEM" not in os.environ: - os.environ["PYCTDEV_ECOSYSTEM"] = "conda" -from pyctdev import * # noqa: api - - -def task_build_website(): - return {'actions': [ - "nbsite generate-rst --org holoviz --project-name colorcet", - "nbsite build --what=html --output=builtdocs --org holoviz --project-name colorcet", - ]} - - -def task_pip_on_conda(): - """Experimental: provide pip build env via conda""" - return {'actions':[ - # some ecosystem=pip build tools must be installed with conda when using conda... - 'conda install -y pip twine wheel rfc3986 keyring', - # ..and some are only available via conda-forge - 'conda install -y -c conda-forge tox virtualenv', - ]} diff --git a/pyproject.toml b/pyproject.toml index 1696485..475b7e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,86 @@ [build-system] requires = [ - "pyct >=0.4.4", "setuptools >=30.3.0", - "wheel", + "setuptools_scm >=6", ] +build-backend = "setuptools.build_meta" + +[project] +name = "colorcet" +dynamic = ["version"] +description = "Collection of perceptually uniform colormaps" +readme = "README.md" +license = { text = "CC-BY License" } +requires-python = ">=3.7" +authors = [ + { name = "James A. Bednar", email = "jbednar@anaconda.com" }, +] +maintainers = [ + { name = "James A. Bednar", email = "jbednar@anaconda.com" }, +] +classifiers = [ + "License :: OSI Approved", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Development Status :: 5 - Production/Stable", +] +dependencies = [] + +[project.urls] +Homepage = "https://colorcet.holoviz.org" +Source = "http://github.com/holoviz/colorcet" +HoloViz = "https://holoviz.org/" + +[project.optional-dependencies] +tests = [ + "pre-commit", + "pytest >=2.8.5", + "pytest-cov", + "packaging", +] +tests_extra = [ + "colorcet[tests]", + "pytest-mpl", # only available on pip and conda-forge +] +examples = [ + "numpy", + "holoviews", + "matplotlib", + "bokeh", +] +tests_examples = [ + "colorcet[examples]", + "nbval", +] +doc = [ + "colorcet[examples]", + "nbsite >=0.8.4", + "sphinx-copybutton", +] +all = [ + "colorcet[tests]", + "colorcet[tests_extra]", + "colorcet[examples]", + "colorcet[doc]", +] + +[tool.setuptools] +# To exclude PNG files in colorcet/tests/baseline +include-package-data = false + +[tool.setuptools.packages.find] +include = ["colorcet"] + +[tool.setuptools_scm] +# Replace with version_file when Python 3.7 is dropped. +write_to = "colorcet/_version.py" + +[tool.pytest.ini_options] +addopts = "-v --pyargs" +norecursedirs = "doc .git dist build _build .ipynb_checkpoints" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 74b403e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[metadata] -license_file = LICENSE.txt - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 2684d1f..0000000 --- a/setup.py +++ /dev/null @@ -1,119 +0,0 @@ -import os -import sys -import shutil -from setuptools import setup, find_packages - -import pyct.build - -def get_setup_version(reponame): - """ - Helper to get the current version from either git describe or the - .version file (if available). - """ - import json - basepath = os.path.split(__file__)[0] - version_file_path = os.path.join(basepath, reponame, '.version') - try: - from colorcet import version - except: - version = None - if version is not None: - return version.Version.setup_version(basepath, reponame, archive_commit="$Format:%h$") - else: - return json.load(open(version_file_path, 'r'))['version_string'] - - -########## dependencies ########## - -install_requires = [ - 'pyct >=0.4.4', -] - -examples = [ - 'numpy', - 'holoviews', - 'matplotlib', - 'bokeh', -] - -tests = [ - 'flake8', - 'nbsmoke >=0.2.6', - 'pytest >=2.8.5', - 'pytest-cov', - 'packaging', -] - -extras_require = { - 'tests': tests, - 'examples': examples, - 'doc': examples + [ - 'nbsite >=0.8.4', - 'sphinx-copybutton', - ], - 'tests_extra': tests + [ - 'pytest-mpl' # only available on pip and conda-forge - ], - # until pyproject.toml/equivalent is widely supported (setup_requires - # doesn't work well with pip) - 'build': [ - 'pyct >=0.4.4', - 'setuptools >=30.3.0', - 'wheel', - ] -} - -extras_require['all'] = sorted(set(sum(extras_require.values(), []))) - -setup_args = dict( - name='colorcet', - description='Collection of perceptually uniform colormaps', - version=get_setup_version('colorcet'), - long_description=open("README.md", mode="r", encoding="utf-8").read(), - long_description_type='text/markdown', - license_files=['LICENSE.txt'], - license='CC-BY License', - classifiers=[ - "License :: OSI Approved", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Development Status :: 5 - Production/Stable", - ], - author="James A. Bednar", - author_email="jbednar@anaconda.com", - maintainer="James A. Bednar", - maintainer_email="jbednar@anaconda.com", - url="https://colorcet.holoviz.org", - project_urls = { - "Bug Tracker": "http://github.com/holoviz/colorcet/issues", - "Documentation": "https://colorcet.holoviz.org", - "Source Code": "http://github.com/holoviz/colorcet", - }, - include_package_data=True, - packages=find_packages(), - python_requires=">=3.7", - install_requires=install_requires, - extras_require=extras_require, - entry_points={ - 'console_scripts': [ - 'colorcet = colorcet.__main__:main' - ] - }, -) - - -if __name__=="__main__": - example_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'colorcet','examples') - if 'develop' not in sys.argv: - pyct.build.examples(example_path, __file__, force=True) - - setup(**setup_args) - - if os.path.isdir(example_path): - shutil.rmtree(example_path) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 981fd67..0000000 --- a/tox.ini +++ /dev/null @@ -1,58 +0,0 @@ -# For use with pyctdev (but should work with tox alone) - -[tox] -envlist = {py37,py38,py39,py310,py311}-{lint,unit,unit_extra,examples,all}-{default}-{dev,pkg} -build = wheel - -[_lint] -description = Lint check python code and notebooks -deps = .[tests] -commands = flake8 - pytest --nbsmoke-lint -k ".ipynb" - -[_unit] -description = There are just some basic unit tests so far -deps = .[tests] -commands = pytest colorcet --cov=colorcet --cov-append --cov-report xml - -[_unit_extra] -description = Run the unit tests with pytest-mpl and matplotlib -deps = .[examples,tests_extra] -commands = pytest colorcet --mpl - -[_examples] -description = Test that the examples run without exceptions -deps = .[examples,tests] -commands = pytest --nbsmoke-run -k ".ipynb" - -[_all] -description = Run all the tests -deps = .[examples, tests] -commands = {[_lint]commands} - {[_unit]commands} - {[_examples]commands} - -[testenv] -changedir = {envtmpdir} - -commands = unit: {[_unit]commands} - unit_extra: {[_unit_extra]commands} - lint: {[_lint]commands} - examples: {[_examples]commands} - all: {[_all]commands} - -deps = unit: {[_unit]deps} - unit_extra: {[_unit_extra]deps} - lint: {[_lint]deps} - examples: {[_examples]deps} - all: {[_all]deps} - -[pytest] -addopts = -v --pyargs -norecursedirs = doc .git dist build _build .ipynb_checkpoints - -[flake8] -include = *.py -exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,.ipynb_checkpoints,run_test.py -ignore = E, - W