From f88006531ad8b5e440b90c80fe274b8229ce50ad Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Fri, 18 Mar 2022 13:34:51 +0000 Subject: [PATCH] Move configuration from setup.py to setup.cfg (#296) Make the move away from setup.py following the recommendations from the Python packaging guides. Moves the requirement listing to setup.cfg as well and will use a script to extract this for conda installing on CI. --- .coveragerc | 2 +- .flake8 | 35 -------- .github/workflows/docs.yml | 77 ++++++++--------- .github/workflows/pypi.yml | 46 ++++++++-- .github/workflows/test.yml | 76 ++++++++-------- .gitignore | 2 +- .isort.cfg | 3 - Makefile | 7 +- env/requirements-build.txt | 4 +- environment.yml | 18 ++-- harmonica/__init__.py | 2 +- harmonica/{version.py => _version.py} | 2 +- harmonica/datasets/sample_data.py | 2 +- pyproject.toml | 20 +++++ pytest.ini | 3 - requirements.txt | 8 -- setup.cfg | 91 ++++++++++++++++++++ setup.py | 77 +---------------- tools/export_requirements.py | 19 ++++ license_notice.py => tools/license_notice.py | 0 20 files changed, 268 insertions(+), 226 deletions(-) delete mode 100644 .flake8 delete mode 100644 .isort.cfg rename harmonica/{version.py => _version.py} (91%) create mode 100644 pyproject.toml delete mode 100644 pytest.ini delete mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 tools/export_requirements.py rename license_notice.py => tools/license_notice.py (100%) diff --git a/.coveragerc b/.coveragerc index a2deea8f4..2debec871 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,5 +2,5 @@ branch = True omit = */tests/* - */_version.py + */_version_generated.py **/__init__.py diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 833e2ea22..000000000 --- a/.flake8 +++ /dev/null @@ -1,35 +0,0 @@ -[flake8] -max-line-length = 88 -max-doc-length = 79 -ignore = - # Too many leading '#' for block comment - E266, - # Line too long (82 > 79 characters) - E501, - # Do not use variables named 'I', 'O', or 'l' - E741, - # Line break before binary operator (conflicts with black) - W503, -exclude = - .git, - __pycache__, - .ipynb_checkpoints, -per-file-ignores = - # disable unused-imports errors on __init__.py - __init__.py: F401 - -# Configure flake8-rst-docstrings -# ------------------------------- -# Add some roles used in our docstrings -rst-roles = - class, - func, - mod, - meth, -# Ignore "Unknown target name" raised on citations -extend-ignore = RST306 - -# Configure flake8-functions -# -------------------------- -# Allow a max of 10 arguments per function -max-parameters-amount = 10 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c8f1e7e14..62cda988b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,7 +4,7 @@ # token stolen if the Action is compromised. See the comments and links here: # https://github.com/pypa/gh-action-pypi-publish/issues/27 # -name: docs +name: documentation # Only build PRs, the main branch, and releases. Pushes to branches will only # be built when a PR is opened. This avoids duplicated buids in PRs comming @@ -32,7 +32,7 @@ jobs: build: runs-on: ubuntu-latest env: - REQUIREMENTS: requirements.txt env/requirements-docs.txt + REQUIREMENTS: env/requirements-build.txt env/requirements-docs.txt PYTHON: 3.9 steps: @@ -61,45 +61,44 @@ jobs: - name: Fetch git tags run: git fetch origin 'refs/tags/*:refs/tags/*' - - name: Setup caching for conda packages - uses: actions/cache@v2 - with: - path: ~/conda_pkgs_dir - key: conda-${{ runner.os }}-${{ env.PYTHON }}-${{ hashFiles('requirements*.txt') }} - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2.0.1 + uses: conda-incubator/setup-miniconda@v2 with: python-version: ${{ env.PYTHON }} - miniconda-version: "latest" - auto-update-conda: true - channels: conda-forge - show-channel-urls: true - activate-environment: testing + miniforge-variant: Mambaforge + use-mamba: true + channels: conda-forge,defaults # Needed for caching use-only-tar-bz2: true - - name: Install requirements + - name: Collect requirements - run-time + run: python tools/export_requirements.py > requirements-full.txt + + - name: Collect requirements - other run: | - requirements_file=requirements-full.txt - if [ ! -z "$REQUIREMENTS" ]; then - echo "Capturing dependencies from $REQUIREMENTS" - for requirement in $REQUIREMENTS - do - cat $requirement >> $requirements_file - done - fi - if [ -f $requirements_file ]; then - echo "Collected dependencies:" - cat $requirements_file - echo "" - conda install --quiet --file $requirements_file python=$PYTHON - else - echo "No requirements defined." - fi + echo "Capturing dependencies from:" + for requirement in $REQUIREMENTS + do + echo " $requirement" + cat $requirement >> requirements-full.txt + done + + - name: List requirements + run: | + echo "Collected dependencies:" + cat requirements-full.txt + + - name: Setup caching for conda packages + uses: actions/cache@v2 + with: + path: ~/conda_pkgs_dir + key: conda-${{ runner.os }}-${{ env.PYTHON }}-${{ hashFiles('requirements-full.txt') }} + + - name: Install requirements + run: mamba install --quiet --file requirements-full.txt python=$PYTHON - name: List installed packages - run: conda list + run: mamba list - name: Build source and wheel distributions run: | @@ -109,23 +108,15 @@ jobs: ls -lh dist/ - name: Install the package - run: pip install --no-deps dist/*.whl - - - name: Copy test data to cache - run: | - echo "Copy data to " $HARMONICA_DATA_DIR/main - set -x -e - mkdir -p $HARMONICA_DATA_DIR/main - cp -r data/* $HARMONICA_DATA_DIR/main - env: - # Define directory where sample data will be copied - HARMONICA_DATA_DIR: ${{ runner.temp }}/cache/harmonica + run: python -m pip install --no-deps dist/*.whl - name: Build the documentation run: make -C doc clean all # Store the docs as a build artifact so we can deploy it later - name: Upload HTML documentation as an artifact + # Only if not a pull request + if: success() && github.event_name != 'pull_request' uses: actions/upload-artifact@v2 with: name: docs-${{ github.sha }} diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9c8d3f61d..4933853e0 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -6,8 +6,10 @@ # name: pypi -# Only run for pushes to the main branch and releases. +# Runs on these events but only publish on pushes to main (to test pypi) and +# releases on: + pull_request: push: branches: - main @@ -21,12 +23,11 @@ defaults: shell: bash jobs: + ############################################################################# - # Publish built wheels and source archives to PyPI and test PyPI - publish: + # Build and check source and wheel distributions + build: runs-on: ubuntu-latest - # Only publish from the origin repository, not forks - if: github.repository_owner == 'fatiando' steps: # Checks-out your repository under $GITHUB_WORKSPACE @@ -63,7 +64,7 @@ jobs: # Change setuptools-scm local_scheme to "no-local-version" so the # local part of the version isn't included, making the version string # compatible with Test PyPI. - sed --in-place "s/node-and-date/no-local-version/g" setup.py + sed --in-place "s/node-and-date/no-local-version/g" pyproject.toml - name: Build source and wheel distributions run: | @@ -75,8 +76,39 @@ jobs: - name: Check the archives run: twine check dist/* + # Store the archives as a build artifact so we can deploy them later + - name: Upload archives as artifacts + # Only if not a pull request + if: success() && github.event_name != 'pull_request' + uses: actions/upload-artifact@v2 + with: + name: pypi-${{ github.sha }} + path: dist + + ############################################################################# + # Publish built wheels and source archives to PyPI and test PyPI + publish: + runs-on: ubuntu-latest + # Only publish from the origin repository, not forks + if: github.repository_owner == 'fatiando' && github.event_name != 'pull_request' + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # The GitHub token is preserved by default but this job doesn't need + # to be able to push to GitHub. + persist-credentials: false + + - name: Download built source and wheel packages + uses: actions/download-artifact@v2 + with: + name: pypi-${{ github.sha }} + path: dist + - name: Publish to Test PyPI - if: success() + # Only publish to TestPyPI when a PR is merged (pushed to main) + if: success() && github.event_name == 'push' uses: pypa/gh-action-pypi-publish@bce3b74dbf8cc32833ffba9d15f83425c1a736e0 with: user: __token__ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec613e8d5..6a461f38b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,32 +21,34 @@ on: # Use bash by default in all jobs defaults: run: - # The -l {0} is necessary for conda environments to be activated - # But this breaks on MacOS if using actions/setup-python: - # https://github.com/actions/setup-python/issues/132 shell: bash jobs: ############################################################################# # Run tests and upload to codecov test: - name: ${{ matrix.os }} py${{ matrix.python }} + name: ${{ matrix.os }} python=${{ matrix.python }} dependencies=${{ matrix.dependencies }} runs-on: ${{ matrix.os }}-latest strategy: # Otherwise, the workflow would stop if a single job fails. We want to # run all of them to catch failures in different combinations. fail-fast: false matrix: - os: [ubuntu, macos, windows] - python: ["3.6", "3.9"] - # If "optional", will install non-required dependencies in the build - # environment. Otherwise, only required dependencies are installed. - dependencies: [""] + os: + - ubuntu + - macos + - windows + python: + - "3.6" + - "3.9" + dependencies: + - latest env: - REQUIREMENTS: requirements.txt env/requirements-tests.txt + REQUIREMENTS: env/requirements-tests.txt # Used to tag codecov submissions OS: ${{ matrix.os }} PYTHON: ${{ matrix.python }} + DEPENDENCIES: ${{ matrix.dependencies }} steps: # Cancel any previous run of the test job @@ -79,6 +81,23 @@ jobs: with: python-version: ${{ matrix.python }} + - name: Collect requirements - run-time + run: python tools/export_requirements.py > requirements-full.txt + + - name: Collect requirements - other + run: | + echo "Capturing dependencies from:" + for requirement in $REQUIREMENTS + do + echo " $requirement" + cat $requirement >> requirements-full.txt + done + + - name: List requirements + run: | + echo "Collected dependencies:" + cat requirements-full.txt + - name: Get the pip cache folder id: pip-cache run: | @@ -88,34 +107,14 @@ jobs: uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} - restore-keys: | - ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} + key: ${{ runner.os }}-pip-${{ hashFiles('requirements-full.txt') }} - name: Install requirements run: | - requirements_file=requirements-full.txt - if [ ! -z "$REQUIREMENTS" ]; then - echo "Capturing dependencies from $REQUIREMENTS" - for requirement in $REQUIREMENTS - do - cat $requirement >> $requirements_file - done - fi - if [ -f $requirements_file ]; then - echo "Collected dependencies:" - cat $requirements_file - echo "" - # Install wheel before anything else so pip can use wheels for - # other packages. - python -m pip install -r env/requirements-build.txt - python -m pip install -r $requirements_file - else - echo "No requirements defined." - fi - - - name: List installed packages - run: python -m pip freeze + # Install the build requirements before anything else so pip can use + # wheels for other packages. + python -m pip install --requirement env/requirements-build.txt + python -m pip install --requirement requirements-full.txt - name: Build source and wheel distributions run: | @@ -125,7 +124,10 @@ jobs: ls -lh dist/ - name: Install the package - run: pip install --no-deps dist/*.whl + run: python -m pip install --no-deps dist/*.whl + + - name: List installed packages + run: python -m pip freeze - name: Copy test data to cache run: | @@ -159,7 +161,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml - env_vars: OS,PYTHON + env_vars: OS,PYTHON,DEPENDENCIES # Don't mark the job as failed if the upload fails for some reason. # It does sometimes but shouldn't be the reason for running # everything again unless something else is broken. diff --git a/.gitignore b/.gitignore index 120d65de6..05205e41e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ doc/sample_data MANIFEST dask-worker-space .coverage.* -harmonica/_version.py +harmonica/_version_generated.py diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 2c0a3fb7f..000000000 --- a/.isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -# Configure isort to be compatible with black -profile=black diff --git a/Makefile b/Makefile index e845c90cf..b537b4e6f 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ PROJECT=harmonica TESTDIR=tmp-test-dir-with-unique-name PYTEST_ARGS=--cov-config=../.coveragerc --cov-report=term-missing --cov=$(PROJECT) --doctest-modules -v --pyargs NUMBATEST_ARGS=--doctest-modules -v --pyargs -m use_numba -LINT_FILES=setup.py $(PROJECT) license_notice.py -STYLE_CHECK_FILES=setup.py $(PROJECT) examples data/examples doc/conf.py license_notice.py +STYLE_CHECK_FILES=setup.py $(PROJECT) examples data/examples doc/conf.py tools help: @echo "Commands:" @@ -51,10 +50,10 @@ isort-check: isort --check $(STYLE_CHECK_FILES) license: - python license_notice.py + python tools/license_notice.py license-check: - python license_notice.py --check + python tools/license_notice.py --check flake8: flake8 $(STYLE_CHECK_FILES) diff --git a/env/requirements-build.txt b/env/requirements-build.txt index cbc50fb63..81e624388 100644 --- a/env/requirements-build.txt +++ b/env/requirements-build.txt @@ -1,5 +1,5 @@ # Requirements to build and check distributions +setuptools>=45 +setuptools_scm>=6.2 twine wheel -setuptools -setuptools_scm diff --git a/environment.yml b/environment.yml index 5a0e53a17..5e79f5b28 100644 --- a/environment.yml +++ b/environment.yml @@ -5,7 +5,12 @@ channels: dependencies: - python==3.8 - pip - - setuptools_scm + # Build + - setuptools>=45 + - setuptools_scm>=6.2 + - wheel + - twine + # Run-time - numpy - pandas - numba @@ -14,17 +19,18 @@ dependencies: - pooch>=0.7.0 - verde>=1.5.0 - xarray - # Development requirements - - boule - - pyproj - - matplotlib - - cartopy>=0.18 + # Testing requirements - pytest - pytest-cov - coverage + - boule + # Documentation requirements - sphinx==3.5.* - sphinx-book-theme==0.0.41 - sphinx-gallery==0.8.* + - pyproj + - matplotlib + - cartopy>=0.18 # Code style checks and autoformat - black==21.12b0 - isort==5.10.* diff --git a/harmonica/__init__.py b/harmonica/__init__.py index 19ee0cfc5..7d1fe6ec3 100644 --- a/harmonica/__init__.py +++ b/harmonica/__init__.py @@ -7,6 +7,7 @@ # # Import functions/classes to make the public API from . import datasets, synthetic +from ._version import __version__ from .equivalent_sources.cartesian import EQLHarmonic, EquivalentSources from .equivalent_sources.gradient_boosted import EquivalentSourcesGB from .equivalent_sources.spherical import EQLHarmonicSpherical, EquivalentSourcesSph @@ -17,7 +18,6 @@ from .gravity_corrections import bouguer_correction from .io import load_icgem_gdf from .isostasy import isostasy_airy -from .version import __version__ def test(doctest=True, verbose=True, coverage=False, figures=False): diff --git a/harmonica/version.py b/harmonica/_version.py similarity index 91% rename from harmonica/version.py rename to harmonica/_version.py index ce6b7d8a0..eea22ba87 100644 --- a/harmonica/version.py +++ b/harmonica/_version.py @@ -8,7 +8,7 @@ Define the __version__ variable as the version number with leading "v" """ # This file is generated automatically by setuptools_scm -from ._version import version +from ._version_generated import version # Add a "v" to the version number __version__ = f"v{version}" diff --git a/harmonica/datasets/sample_data.py b/harmonica/datasets/sample_data.py index 7e360365c..11eec7722 100644 --- a/harmonica/datasets/sample_data.py +++ b/harmonica/datasets/sample_data.py @@ -12,7 +12,7 @@ import pooch import xarray as xr -from ..version import __version__ as version +from .._version import __version__ as version REGISTRY = pooch.create( path=pooch.os_cache("harmonica"), diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6a5b0e2aa --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +# Specify that we use setuptools and setuptools_scm (to generate the version +# string). Actual configuration is in setup.py and setup.cfg. +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +version_scheme = "post-release" +local_scheme = "node-and-date" +write_to = "harmonica/_version_generated.py" + +[tool.pytest.ini_options] +markers = [ + "use_numba: mark test functions that call Numba jitted functions" +] + +# Make sure isort and Black are compatible +[tool.isort] +profile = "black" +multi_line_output = 3 diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 9ce91daec..000000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -markers = - use_numba: mark test functions that call Numba jitted functions diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1abc0dbfa..000000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -numpy -pandas -scipy -scikit-learn -numba -pooch>=0.7.0 -xarray -verde>=1.5.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..8c7b7eb43 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,91 @@ +[metadata] +name = harmonica +fullname = Harmonica +description = "Forward modeling, inversion, and processing gravity and magnetic data" +long_description = file: README.rst +long_description_content_type = text/x-rst +author = The Harmonica Developers +author_email = fatiandoaterra@protonmail.com +maintainer = "Santiago Soler" +maintainer_email = santiago.r.soler@gmail.com +license = BSD 3-Clause License +license_file = LICENSE.txt +platform = any +keywords = geoscience, geophysics +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Science/Research + Intended Audience :: Developers + Intended Audience :: Education + License :: OSI Approved :: BSD License + Natural Language :: English + Operating System :: OS Independent + Topic :: Scientific/Engineering + Topic :: Software Development :: Libraries + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 +url = https://github.com/fatiando/harmonica +project_urls = + Documentation = https://www.fatiando.org/harmonica + Release Notes = https://github.com/fatiando/harmonica/releases + Bug Tracker = https://github.com/fatiando/harmonica/issues + Source Code = https://github.com/fatiando/harmonica + +[options] +zip_safe = True +include_package_data = True +packages = find: +python_requires = >=3.6 +install_requires = + numpy + pandas + scipy + scikit-learn + numba + pooch>=0.7.0 + xarray + verde>=1.5.0 + +[options.package_data] +harmonica.tests = data/*, baseline/* +harmonica.datasets = registry.txt + +[flake8] +max-line-length = 88 +max-doc-length = 79 +ignore = + # Too many leading '#' for block comment + E266, + # Line too long (82 > 79 characters) + E501, + # Do not use variables named 'I', 'O', or 'l' + E741, + # Line break before binary operator (conflicts with black) + W503, +exclude = + .git, + __pycache__, + .ipynb_checkpoints, +per-file-ignores = + # disable unused-imports errors on __init__.py + __init__.py: F401 + +# Configure flake8-rst-docstrings +# ------------------------------- +# Add some roles used in our docstrings +rst-roles = + class, + func, + mod, + meth, + ref, +# Ignore "Unknown target name" raised on citations +extend-ignore = RST306 + +# Configure flake8-functions +# -------------------------- +# Allow a max of 10 arguments per function +max-parameters-amount = 10 diff --git a/setup.py b/setup.py index 70c718617..a275f1301 100644 --- a/setup.py +++ b/setup.py @@ -5,80 +5,11 @@ # This code is part of the Fatiando a Terra project (https://www.fatiando.org) # """ -Build and install the project. - +Setup script for the Python package. +Metadata and build configuration are defined in setup.cfg Uses setuptools-scm to manage version numbers using git tags. """ -from setuptools import find_packages, setup - -NAME = "harmonica" -FULLNAME = "Harmonica" -AUTHOR = "The Harmonica Developers" -AUTHOR_EMAIL = "leouieda@gmail.com" -MAINTAINER = "Leonardo Uieda" -MAINTAINER_EMAIL = AUTHOR_EMAIL -LICENSE = "BSD License" -URL = "https://github.com/fatiando/harmonica" -DESCRIPTION = "Forward modeling, inversion, and processing gravity and magnetic data " -KEYWORDS = "" -with open("README.rst") as f: - LONG_DESCRIPTION = "".join(f.readlines()) -CLASSIFIERS = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Topic :: Scientific/Engineering", - "Topic :: Software Development :: Libraries", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3 :: Only", - f"License :: OSI Approved :: {LICENSE}", -] -PLATFORMS = "Any" -PACKAGES = find_packages(exclude=["doc"]) -SCRIPTS = [] -PACKAGE_DATA = { - "harmonica.datasets": ["registry.txt"], - "harmonica.tests": ["data/*", "baseline/*"], -} -with open("requirements.txt") as f: - INSTALL_REQUIRES = f.readlines() -PYTHON_REQUIRES = ">=3.6" - -# Configuration for setuptools-scm -SETUP_REQUIRES = ["setuptools_scm"] -USE_SCM_VERSION = { - "relative_to": __file__, - "version_scheme": "post-release", - "local_scheme": "node-and-date", - "write_to": f"{NAME}/_version.py", -} - +from setuptools import setup if __name__ == "__main__": - setup( - name=NAME, - fullname=FULLNAME, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - use_scm_version=USE_SCM_VERSION, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - license=LICENSE, - url=URL, - platforms=PLATFORMS, - scripts=SCRIPTS, - packages=PACKAGES, - package_data=PACKAGE_DATA, - classifiers=CLASSIFIERS, - keywords=KEYWORDS, - install_requires=INSTALL_REQUIRES, - python_requires=PYTHON_REQUIRES, - setup_requires=SETUP_REQUIRES, - ) + setup() diff --git a/tools/export_requirements.py b/tools/export_requirements.py new file mode 100644 index 000000000..bf3d9028d --- /dev/null +++ b/tools/export_requirements.py @@ -0,0 +1,19 @@ +# Copyright (c) 2018 The Harmonica Developers. +# Distributed under the terms of the BSD 3-Clause License. +# SPDX-License-Identifier: BSD-3-Clause +# +# This code is part of the Fatiando a Terra project (https://www.fatiando.org) +# +""" +Export the run-time requirements from setup.cfg to a requirement.txt format. +Modified from https://github.com/Unidata/MetPy +""" +import configparser + +# Read the setup.cfg +config = configparser.ConfigParser() +config.read("setup.cfg") + +print("# Run-time dependencies") +for package in config["options"]["install_requires"].strip().split("\n"): + print(package.strip()) diff --git a/license_notice.py b/tools/license_notice.py similarity index 100% rename from license_notice.py rename to tools/license_notice.py