diff --git a/.github/workflows/first_pull_request.yml b/.github/workflows/first_pull_request.yml new file mode 100644 index 0000000..c33075f --- /dev/null +++ b/.github/workflows/first_pull_request.yml @@ -0,0 +1,46 @@ +name: First Pull Request + +on: + pull_request_target: + types: + - opened + +jobs: + welcome: + name: Welcome + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + // Get a list of all issues created by the PR opener + // See: https://octokit.github.io/rest.js/#pagination + const creator = context.payload.sender.login + const opts = github.rest.issues.listForRepo.endpoint.merge({ + ...context.issue, + creator, + state: 'all' + }) + const issues = await github.paginate(opts) + + for (const issue of issues) { + if (issue.number === context.issue.number) { + continue + } + + if (issue.pull_request) { + return // Creator is already a contributor. + } + } + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `**Welcome**, new contributor! + + It appears that this is your first Pull Request. To give credit where it's due, we ask that you add your information to the \`AUTHORS.rst\`: + - [ ] The relevant author information has been added to \`AUTHORS.rst\` + + Please make sure you've read our [contributing guide](CONTRIBUTING.rst). We look forward to reviewing your Pull Request shortly ✨` + }) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5bf10b1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,51 @@ +name: roocs-grids Testing Suite + +on: + push: + branches: + - master + pull_request: + +jobs: + black: + runs-on: ubuntu-latest + strategy: + matrix: + tox-env: + - black + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ github.token }} + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.8" + - name: Install linters + run: | + pip install black ruff + - name: Run linting suite + run: | + black --check roocs_grids + ruff roocs_grids + + test: + name: Pip with Python${{ matrix.python-version }} + needs: black + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install roocs-grids + run: | + pip install -e ".[dev]" + - name: Run tests + run: | + pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 524081d..594fe86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,43 +2,46 @@ default_language_version: python: python3 repos: -- repo: https://github.com/asottile/pyupgrade + - repo: https://github.com/asottile/pyupgrade rev: v3.10.1 hooks: - id: pyupgrade args: [ '--py39-plus' ] -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: debug-statements - - id: mixed-line-ending -- repo: https://github.com/psf/black + - id: trailing-whitespace + exclude: setup.cfg + - id: end-of-file-fixer + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: mixed-line-ending + - repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 + hooks: + - id: toml-sort-fix + - repo: https://github.com/psf/black rev: 23.7.0 hooks: - - id: black - args: ["--target-version", "py39"] -- repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + - id: black + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.1 hooks: - - id: flake8 - args: ['--ignore=E501'] -- repo: https://github.com/PyCQA/isort + - id: ruff + - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - - id: isort - args: ['--profile', 'black'] -#- repo: https://github.com/pycqa/pydocstyle -# rev: 6.1.1 -# hooks: -# - id: pydocstyle -# args: ["--convention=numpy"] -- repo: meta - hooks: - - id: check-hooks-apply - - id: check-useless-excludes + - id: isort + args: ['--profile', 'black'] + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.27.0 + hooks: + - id: check-github-workflows + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes ci: autofix_commit_msg: | diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..e7e1ac8 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,15 @@ +======= +Credits +======= + +Development Leads +----------------- + +* Ag Stephens `@agstephens `_ +* Martin Schupfner `@sol1105 `_ + +Contributors +------------- + +* Carsten Ehbrecht `@cehbrecht `_ +* Trevor James Smith `@Zeitsperre `_ diff --git a/HISTORY.rst b/HISTORY.rst index b55e117..eb3b97c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,18 @@ Version History =============== +v0.1.2 (2023-10-25) +------------------- + +Bug Fixes +^^^^^^^^^ + +* Added basic CI workflows +* Replaced setuptools with flit and pyproject.toml +* Added AUTHORS.rst and bump2version configuration +* Linting with ruff +* Made testing safer with non-POSIX environments + v0.1.1 (2023-10-20) ------------------- diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index dede379..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -include *.txt -include *.rst -include LICENSE - -recursive-include aux * -recursive-include roocs_grids * - -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] diff --git a/README.rst b/README.rst index e06f904..7edf6ea 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ roocs-grids =========== -Grid definitions for the roocs regridder: +Grid definitions for the roocs regridder. * Grids suggested for CMIP6: `Link `_ * Grids used for the `IPCC Atlas `_: `Link `_ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..db807b8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,93 @@ +[build-system] +build-backend = "flit_core.buildapi" +requires = ["flit_core >=3.8,<4"] + +[project] +name = "roocs_grids" +keywords = ["roocs_grids", "regrid", "grid", "interpolate", "cmip"] +authors = [ + {name = "Ag Stephens", email = "ag.stephens@stfc.ac.uk"}, + {name = "Martin Schupfner", email = "schupfner@dkrz.de"} +] +maintainers = [ + {name = "Carsten Ehbrecht", email = "ehbrecht@dkrz.de"}, + {name = "Trevor James Smith", email = "smith.trevorj@ouranos.ca"} +] +license = {file = "LICENSE"} +python_requires = ">=3.8.0" +dynamic = ["description", "version"] +dependencies = [] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: OS Independent", + "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", + "Topic :: Scientific/Engineering :: Atmospheric Science", + "Topic :: Scientific/Engineering :: GIS", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +[project.optional-dependencies] +dev = [ + "black", + "bump2version", + "clisops", + "isort", + "pytest>=7.0.0", + "ruff>=0.1.0", + "xarray" +] + +[project.urls] +"Source" = "https://github.com/roocs/roocs-grids/" +"Changelog" = "https://github.com/roocs/roocs-grids/blob/main/HISTORY.rst" +"Issue tracker" = "https://github.com/roocs/roocs-grids/issues" +"About ROOCS" = "https://roocs.github.io/" + +[tool.black] +target-version = [ + "py38", + "py39", + "py310", + "py311" +] + +[tool.flit.sdist] +include = [ + "AUTHORS.rst", + "HISTORY.rst", + "LICENSE", + "README.rst", + "tests/*.py" +] +exclude = [ + "**/*.py[co]", + "**/__pycache__", + ".*", + "scripts/*" +] + +[tool.isort] +profile = "black" +py_version = 38 + +[tool.ruff] +src = ["roocs_grids"] +line-length = 88 +target-version = "py38" +ignore = ["E501"] + +[tool.ruff.format] +line-ending = "auto" + +[tool.ruff.isort] +known-first-party = ["roocs_grids"] +lines-after-imports = 1 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 951705b..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -setuptools>=68.0.0 diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index d7378b1..0000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest>=7.0.0 -bumpversion -wheel -twine diff --git a/roocs_grids/__init__.py b/roocs_grids/__init__.py index 0053edf..780f047 100644 --- a/roocs_grids/__init__.py +++ b/roocs_grids/__init__.py @@ -1,6 +1,12 @@ -import os +"""Grid definitions for the roocs regridder.""" +import pathlib -pkg_dir = os.path.dirname(__file__) +__author__ = "Martin Schupfner" +__email__ = "schupfner@dkrz.de" +__copyright__ = "Copyright 2018 United Kingdom Research and Innovation" +__version__ = "0.1.2" + +pkg_dir = pathlib.Path(__file__).parent.absolute() grid_dict = { "0pt25deg": "cmip6_720x1440_scrip.20181001.nc", @@ -76,11 +82,11 @@ " 384x768 nlatxnlon. Associated to a T255 spectral grid representation.", } -grids_dir = os.path.join(pkg_dir, "grids") +grids_dir = pathlib.Path(pkg_dir).joinpath("grids") -def get_grid_file(grid_id): +def get_grid_file(grid_id: str) -> pathlib.Path: if grid_id not in grid_dict: - raise Exception(f"Unknown grid id: {grid_id}") + raise FileNotFoundError(f"Unknown grid id: {grid_id}") - return os.path.join(grids_dir, grid_dict[grid_id]) + return pathlib.Path(grids_dir).joinpath(grid_dict[grid_id]) diff --git a/aux/create_grid_file.py b/scripts/create_grid_file.py similarity index 87% rename from aux/create_grid_file.py rename to scripts/create_grid_file.py index 65e231a..3c5f30e 100644 --- a/aux/create_grid_file.py +++ b/scripts/create_grid_file.py @@ -2,9 +2,6 @@ from pathlib import Path import xarray as xr - -# FIXME: This is a circular dependency, as clisops will be listing roocs-grids as a dependency -# this must be addressed before this module can be used. from clisops import core as clore from roocs_grids import grids_dir, pkg_dir diff --git a/aux/retrieve_grids.sh b/scripts/retrieve_grids.sh similarity index 100% rename from aux/retrieve_grids.sh rename to scripts/retrieve_grids.sh diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..780f472 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +[bumpversion] +current_version = 0.1.2 +commit = True +tag = False +parse = (?P\d+)\.(?P\d+).(?P\d+) +serialize = + {major}.{minor}.{patch} + +[bumpversion:file:roocs_grids/__init__.py] +search = __version__ = "{current_version}" +replace = __version__ = "{new_version}" diff --git a/setup.py b/setup.py deleted file mode 100644 index 0cd9b70..0000000 --- a/setup.py +++ /dev/null @@ -1,70 +0,0 @@ -"""The setup script.""" - -__author__ = "Ag Stephens" -__contact__ = "ag.stephens@stfc.ac.uk" -__copyright__ = "Copyright 2018 United Kingdom Research and Innovation" -__version__ = "0.1.1" - -import os - -from setuptools import find_packages, setup - -# One strategy for storing the overall version is to put it in the top-level -# package's __init__ but Nb. __init__.py files are not needed to declare -# packages in Python 3 - -# Populate long description setting with content of README -# -# Use markdown format read me file as GitHub will render it automatically -# on package page -here = os.path.abspath(os.path.dirname(__file__)) -#_long_description = open(os.path.join(here, "README.rst")).read() -_long_description = "roocs-grids: Grid definitions for the roocs regridder" - -requirements = [line.strip() for line in open("requirements.txt")] - -setup_requirements = [ - "pytest-runner", -] - -test_requirements = ["pytest"] - -setup( - author=__author__, - author_email=__contact__, - # See: - # https://www.python.org/dev/peps/pep-0301/#distutils-trove-classification - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: OS Independent", - "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", - "Topic :: Scientific/Engineering", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - description="Grid definitions for the roocs regridder", - license_files=["LICENSE"], - python_requires=">=3.8.0", - install_requires=requirements, - long_description=_long_description, - long_description_content_type="text/x-rst", - include_package_data=True, - keywords="roocs_grids, regrid, grid, interpolate, cmip", - name="roocs_grids", - packages=find_packages(include=["roocs_grids"]), - setup_requires=setup_requirements, - test_suite="tests", - tests_require=test_requirements, - package_data={"roocs_grids": ["grids/*"]}, - url="https://github.com/roocs/roocs-grids", - version=__version__, - zip_safe=False, -) diff --git a/tests/test_roocs_grids.py b/tests/test_roocs_grids.py index e74a6a1..1e711c3 100644 --- a/tests/test_roocs_grids.py +++ b/tests/test_roocs_grids.py @@ -1,5 +1,5 @@ import os -from glob import glob +import pathlib import pytest @@ -11,27 +11,29 @@ def test_get_grid_file(): get_grid_file("rubbish") gf = get_grid_file("0pt25deg") - assert os.path.isfile(gf) - assert gf.endswith("roocs_grids/grids/cmip6_720x1440_scrip.20181001.nc") + assert gf.is_file() + assert gf.name.endswith("cmip6_720x1440_scrip.20181001.nc") @pytest.mark.parametrize( "grid_id,filename", [(key, grid_dict[key]) for key in grid_dict] ) def test_grid_avail(grid_id, filename): - with pytest.raises(Exception, match="Unknown grid id: rubbish"): + with pytest.raises(FileNotFoundError, match="Unknown grid id: rubbish"): get_grid_file("rubbish") gf = get_grid_file(grid_id) assert os.path.isfile(gf) - assert gf.endswith(f"roocs_grids/grids/{filename}") + assert gf.as_posix().endswith( + pathlib.Path("roocs_grids/grids").joinpath(filename).as_posix() + ) assert grid_annotations[grid_id] != "" def test_dict_completeness(): """Test that grid_annotations and grid_dict include all available grids.""" # Find all files in the grids directory - grid_files = [os.path.basename(f) for f in glob(f"{grids_dir}/*")] + grid_files = [f.name for f in grids_dir.glob("*")] # Make sure they exist in grid_dict # (test_grid_avail already tests that all grids have annotations)