From b3949e5763c3e58df58273b43dc591a029f9417b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 27 Apr 2023 06:52:48 -0600 Subject: [PATCH] refactor: use pyproject.toml Also includes: - Bump to the min python-dateutil version to bring it into line w/ our doc dependencies - Adding @gadomski as a maintainer - Remove the unused, undocumented `validation` extra - Rework the min_requirement CI check --- .github/workflows/continuous-integration.yml | 37 ++---- CHANGELOG.md | 1 + docs/contributing.rst | 5 +- docs/environment.yml | 3 +- pyproject.toml | 122 +++++++++++++++++-- requirements-dev.txt | 19 --- requirements-docs.txt | 14 --- requirements-min.txt | 3 - scripts/check-minimum-requirements | 44 ------- scripts/install-min-requirements | 43 +++++++ scripts/test | 2 +- setup.cfg | 25 ---- setup.py | 61 ---------- 13 files changed, 168 insertions(+), 211 deletions(-) delete mode 100644 requirements-dev.txt delete mode 100644 requirements-docs.txt delete mode 100644 requirements-min.txt delete mode 100755 scripts/check-minimum-requirements create mode 100755 scripts/install-min-requirements delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ef4a0268..e2c49725 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -24,31 +24,19 @@ jobs: - macos-latest steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: "pip" - cache-dependency-path: | - setup.py - requirements-dev.txt - - - name: Install package - run: pip install . - - - name: Install dev requirements - run: pip install -r requirements-dev.txt - + - name: Install package with dev requirements + run: pip install .[dev] - name: Run pre-commit run: pre-commit run --all-files - - name: Run pytest run: pytest -Werror -s --block-network --cov pystac_client --cov-report term-missing - - name: Run coverage run: coverage xml - - name: Upload All coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -65,15 +53,10 @@ jobs: with: python-version: 3.9 cache: "pip" - cache-dependency-path: "requirements-min.txt" + - name: Install with dev requirements + run: pip install .[dev] - name: Install minimum requirements - run: pip install -r requirements-min.txt - - name: Install - run: pip install . - - name: Install dev requirements - run: pip install -r requirements-dev.txt - - name: Check minimum requirements - run: scripts/check-minimum-requirements + run: ./scripts/install-min-requirements - name: Test run: ./scripts/test @@ -109,13 +92,10 @@ jobs: with: python-version: 3.9 cache: "pip" - cache-dependency-path: "setup.py" - name: Install - run: pip install . + run: pip install .[dev] - name: Install any pre-releases of pystac run: pip install -U --pre pystac - - name: Install dev requirements - run: pip install -r requirements-dev.txt - name: Test run: ./scripts/test @@ -128,8 +108,5 @@ jobs: with: python-version: 3.9 cache: "pip" - cache-dependency-path: "setup.py" - name: Install - run: pip install . - - name: Install dev and docs requirements - run: pip install -r requirements-dev.txt -r requirements-docs.txt + run: pip install .[dev,docs] diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ef76df..e5613157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Switched to Ruff from isort/flake8 [#457](https://github.com/stac-utils/pystac-client/pull/457) - Move to `FutureWarning` from `DeprecationWarning` for item search interface functions that are to be removed [#464](https://github.com/stac-utils/pystac-client/pull/464) - Consolidate contributing docs into one place [#478](https://github.com/stac-utils/pystac-client/issues/478) +- Use `pyproject.toml` instead of `setup.py` [#501](https://github.com/stac-utils/pystac-client/pull/501) ### Fixed diff --git a/docs/contributing.rst b/docs/contributing.rst index 512b3b9d..b4be82d6 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -14,8 +14,7 @@ the library as an "editable link", then install the development dependencies: $ git clone git@github.com:your_user_name/pystac-client.git $ cd pystac - $ pip install -e . - $ pip install -r requirements-dev.txt + $ pip install -e '.[dev]' Testing ^^^^^^^ @@ -102,7 +101,7 @@ Python documentation requirements via pip, then use the ``build-docs`` script: .. code-block:: bash - $ pip install -r requirements-docs.txt + $ pip install -e '.[docs]' $ scripts/build-docs CHANGELOG diff --git a/docs/environment.yml b/docs/environment.yml index d3fc870d..a75f8be6 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -9,5 +9,4 @@ dependencies: - python=3.9 - pip - pip: - - -r ../requirements-docs.txt - - -e ../ + - -e ../[docs] diff --git a/pyproject.toml b/pyproject.toml index 4b149869..2898947a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,119 @@ -[tool.ruff] -ignore = [ - "E722", - "E731", +[project] +name = "pystac-client" +description = "Python library for working with SpatioTemporal Asset Catalog (STAC) APIs." +readme = "README.md" +authors = [ + { name = "Jon Duckworth", email = "duckontheweb@gmail.com" }, + { name = "Matthew Hanson", email = "matt.a.hanson@gmail.com" }, ] -line-length = 88 -select = [ - "E", - "F", - "W", +maintainers = [{ name = "Pete Gadomski", email = "pete.gadomski@gmail.com" }] +keywords = ["pystac", "imagery", "raster", "catalog", "STAC"] +license = { text = "Apache-2.0" } +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + "Natural Language :: English", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Topic :: Scientific/Engineering :: GIS", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", +] +requires-python = ">=3.8" +dependencies = [ + "requests>=2.28.2", + "pystac[validation]>=1.7.2", + "python-dateutil>=2.8.2", ] +dynamic = ["version"] + +[project.scripts] +stac-client = "pystac_client.cli:cli" + +[project.optional-dependencies] +dev = [ + "black~=23.3.0", + "codespell~=2.2.4", + "coverage~=7.2", + "doc8~=1.1.1", + "importlib-metadata~=6.6.0", + "mypy~=1.2", + "orjson~=3.8", + "pre-commit~=3.2", + "pytest-benchmark~=4.0.0", + "pytest-console-scripts~=1.3.1", + "pytest-cov~=4.0.0", + "pytest-recording~=0.12.2", + "pytest~=7.3.1", + "recommonmark~=0.7.1", + "requests-mock~=1.10.0", + "ruff==0.0.263", + "tomli~=2.0; python_version<'3.11'", + "types-python-dateutil~=2.8.19", + "types-requests~=2.28.11", +] +docs = [ + "Sphinx~=6.2", + "boto3~=1.26", + "geojson~=3.0.1", + "geopandas~=0.12.2", + "hvplot~=0.8.3", + "ipykernel~=6.22", + "ipython~=8.12", + "jinja2<4.0", + "matplotlib~=3.7.1", + "myst-parser~=1.0.0", + "nbsphinx~=0.9", + "pydata-sphinx-theme~=0.13", + "pygeoif~=1.0", + "sphinxcontrib-fulltoc~=1.2", +] + +[project.urls] +homepage = "https://github.com/stac-utils/pystac-client" +documentation = "https://pystac-client.readthedocs.io" +repository = "https://github.com/stac-utils/pystac-client.git" +changelog = "https://github.com/stac-utils/pystac-client/blob/main/CHANGELOG.md" +discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software" + +[tool.setuptools.packages.find] +include = ["pystac_client*"] +exclude = ["tests*"] + +[tool.setuptools.dynamic] +version = { attr = "pystac_client.version.__version__" } + +[tool.doc8] +ignore-path = "docs/_build,docs/tutorials" +max-line-length = 130 + +[tool.ruff] +ignore = ["E722", "E731"] +line-length = 88 +select = ["E", "F", "W"] [tool.ruff.per-file-ignores] "__init__.py" = ["F401"] "test_item_search.py" = ["E501"] + +[tool.pytest.ini_options] +markers = "vcr: records network activity" +addopts = "--benchmark-skip -Werror --block-network" + +[tool.mypy] +show_error_codes = true +strict = true + +[[tool.mypy.overrides]] +module = ["jinja2"] +ignore_missing_imports = true + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 83eafb5a..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,19 +0,0 @@ -black~=23.3.0 -codespell~=2.2.4 -coverage~=7.2 -doc8~=1.1.1 -importlib-metadata~=6.6.0 -jsonschema~=4.17.3 -mypy~=1.2 -orjson==3.8.11 -pre-commit==3.2.2 -pytest-benchmark~=4.0.0 -pytest-console-scripts~=1.3.1 -pytest-cov~=4.0.0 -pytest-recording~=0.12.2 -pytest~=7.3.1 -recommonmark~=0.7.1 -requests-mock~=1.10.0 -ruff==0.0.263 -types-python-dateutil~=2.8.19 -types-requests~=2.28.11 diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index ca7c1199..00000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,14 +0,0 @@ -ipykernel~=6.22 -ipython~=8.12 -Sphinx~=6.2 -sphinxcontrib-fulltoc~=1.2 -myst-parser~=1.0.0 -nbsphinx~=0.9 -jinja2<4.0 -geopandas~=0.12.2 -hvplot~=0.8.3 -matplotlib~=3.7.1 -geojson~=3.0.1 -pygeoif~=1.0 -pydata-sphinx-theme~=0.13 -boto3~=1.26 \ No newline at end of file diff --git a/requirements-min.txt b/requirements-min.txt deleted file mode 100644 index da578f2a..00000000 --- a/requirements-min.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests==2.28.2 -pystac==1.7.2 -python-dateutil==2.7.0 \ No newline at end of file diff --git a/scripts/check-minimum-requirements b/scripts/check-minimum-requirements deleted file mode 100755 index cc4db6ac..00000000 --- a/scripts/check-minimum-requirements +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from pathlib import Path - -import importlib_metadata -from packaging.requirements import Requirement - -requirements_min = Path(__file__).parents[1] / "requirements-min.txt" -with open(requirements_min) as file: - min_requirements = [Requirement(line) for line in file] -min_requirements = dict( - (requirement.name, requirement) for requirement in min_requirements -) - -package_requirements = [ - Requirement(requirement) - for requirement in importlib_metadata.requires("pystac_client") -] -incorrect_requirements = list() -for package_requirement in package_requirements: - if package_requirement.marker is not None: - continue - if package_requirement.name not in min_requirements: - incorrect_requirements.append((package_requirement, None)) - continue - min_requirement = min_requirements[package_requirement.name] - for package_specifier, min_specifier in zip( - package_requirement.specifier, min_requirement.specifier - ): - if ( - package_specifier.operator == ">=" - and package_specifier.version != min_specifier.version - ): - incorrect_requirements.append((package_requirement, min_requirement)) - -if incorrect_requirements: - print("ERROR: Incorrect min-requirements.txt!") - for package_requirement, min_requirement in incorrect_requirements: - print(f"- package: {package_requirement}, min: {min_requirement}") - sys.exit(1) -else: - print("OK") - sys.exit(0) diff --git a/scripts/install-min-requirements b/scripts/install-min-requirements new file mode 100755 index 00000000..a2849462 --- /dev/null +++ b/scripts/install-min-requirements @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +"""Installs the minimum version of all stactools dependencies, with pip. + +Assumptions: +- You've installed the development dependencies: `pip install '.[dev]'` +- All of the dependencies in pyproject.toml are specified with `>=` + +For more context on the approach and rationale behind testing against minimum +requirements, see +https://www.gadom.ski/2022/02/18/dependency-protection-with-python-and-github-actions.html. + +""" + +import subprocess +import sys +from pathlib import Path + +from packaging.requirements import Requirement + +assert sys.version_info[0] == 3 +if sys.version_info[1] < 11: + import tomli as toml +else: + import tomllib as toml + + +root = Path(__file__).parents[1] +with open(root / "pyproject.toml", "rb") as f: + pyproject_toml = toml.load(f) +requirements = [] +for install_requires in filter( + bool, + (i.strip() for i in pyproject_toml["project"]["dependencies"]), +): + requirement = Requirement(install_requires) + assert len(requirement.specifier) == 1 + specifier = list(requirement.specifier)[0] + assert specifier.operator == ">=" + install_requires = install_requires.replace(">=", "==") + requirements.append(install_requires) + +subprocess.run(["pip", "install", *requirements]) diff --git a/scripts/test b/scripts/test index 4b723656..3c468629 100755 --- a/scripts/test +++ b/scripts/test @@ -20,7 +20,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then ./scripts/lint ./scripts/format # Test suite with coverage enabled - pytest -Werror -s --block-network --cov pystac_client --cov-report term-missing + pytest --cov pystac_client --cov-report term-missing coverage xml fi fi diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3f37514d..00000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[metadata] -license_file = LICENSE - -[doc8] -ignore-path=docs/_build,docs/tutorials -max-line-length=130 - -[tool:pytest] -markers = - vcr: records network activity -addopts = - --benchmark-skip - -[mypy] -show_error_codes = True -strict = True - -[mypy-jinja2.*] -ignore_missing_imports = True - -[mypy-jsonschema.*] -ignore_missing_imports = True - -[mypy-setuptools.*] -ignore_missing_imports = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 0bcd1dd1..00000000 --- a/setup.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -from glob import glob -from imp import load_source - -from setuptools import find_packages, setup - -__version__ = load_source( - "pystac_client.version", "pystac_client/version.py" -).__version__ - -from os.path import basename, splitext - -here = os.path.abspath(os.path.dirname(__file__)) - -with open(os.path.join(here, "README.md")) as readme_file: - readme = readme_file.read() - -setup( - name="pystac-client", - version=__version__, - description=( - "Python library for working with Spatiotemporal Asset Catalog (STAC)." - ), - long_description=readme, - long_description_content_type="text/markdown", - author="Jon Duckworth, Matthew Hanson", - author_email="duckontheweb@gmail.com, matt.a.hanson@gmail.com", - url="https://github.com/stac-utils/pystac-client.git", - packages=find_packages(exclude=("tests",)), - py_modules=[splitext(basename(path))[0] for path in glob("pystac_client/*.py")], - include_package_data=True, - package_data={"pystac_client": ["py.typed"]}, - python_requires=">=3.8", - install_requires=[ - "requests>=2.28.2", - "pystac>=1.7.2", - "python-dateutil>=2.7.0", - ], - extras_require={"validation": ["jsonschema>=4.5.1"]}, - license="Apache Software License 2.0", - zip_safe=False, - keywords=["pystac", "imagery", "raster", "catalog", "STAC"], - classifiers=[ - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Operating System :: OS Independent", - "Natural Language :: English", - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Topic :: Scientific/Engineering :: GIS", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - test_suite="tests", - entry_points={"console_scripts": ["stac-client=pystac_client.cli:cli"]}, -)