diff --git a/.github/workflows/cd-pypi.yml b/.github/workflows/cd-pypi.yml index bd03198..44b14e9 100644 --- a/.github/workflows/cd-pypi.yml +++ b/.github/workflows/cd-pypi.yml @@ -2,7 +2,7 @@ name: cd on: push: tags: - - '**' + - '**' jobs: pypi: uses: ecmwf/reusable-workflows/.github/workflows/cd-pypi.yml@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad93595..9ed1e9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,26 +3,26 @@ on: # Trigger the workflow on push to master or develop, except tag creation push: branches: - - 'main' - - 'develop' + - 'main' + - 'develop' tags-ignore: - - '**' + - '**' paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" # Trigger the workflow on pull request pull_request: paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" # Trigger the workflow manually workflow_dispatch: # Trigger after public PR approved for CI pull_request_target: types: [labeled] paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" jobs: # Run CI including downstream packages on self-hosted runners downstream-ci: diff --git a/.github/workflows/legacy-ci.yml b/.github/workflows/legacy-ci.yml index 09b98ad..0a5fc30 100644 --- a/.github/workflows/legacy-ci.yml +++ b/.github/workflows/legacy-ci.yml @@ -2,14 +2,14 @@ name: legacy-ci on: push: branches: - - main - - develop + - main + - develop tags: - - "*" + - "*" pull_request: branches: - - main - - develop + - main + - develop pull_request_target: types: [labeled] workflow_dispatch: @@ -24,13 +24,13 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha || github.ref }} - - uses: actions/setup-python@v4 - with: - python-version: 3.x - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.0 documentation: if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} runs-on: ubuntu-latest @@ -38,22 +38,22 @@ jobs: run: shell: bash -l {0} steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha || github.ref }} - - name: Install Conda environment with Micromamba - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: tests/environment-unit-tests.yml - environment-name: DEVELOP - channels: conda-forge - cache-env: true - cache-env-key: ubuntu-latest-3.10 - extra-specs: | - python=3.10 - - name: Install package - run: | - python -m pip install --no-deps -e . - - name: Build documentation - run: | - make docs-build + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Install Conda environment with Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: tests/environment-unit-tests.yml + environment-name: DEVELOP + channels: conda-forge + cache-env: true + cache-env-key: ubuntu-latest-3.10 + extra-specs: | + python=3.10 + - name: Install package + run: | + python -m pip install --no-deps -e . + - name: Build documentation + run: | + make docs-build diff --git a/.github/workflows/notify-new-issue.yml b/.github/workflows/notify-new-issue.yml index 3593edc..4896baa 100644 --- a/.github/workflows/notify-new-issue.yml +++ b/.github/workflows/notify-new-issue.yml @@ -2,12 +2,12 @@ name: Notify new issue on: issues: types: - - "opened" + - "opened" jobs: notify: runs-on: ubuntu-latest steps: - - name: Notify new issue - uses: ecmwf/notify-teams-issue@v1 - with: - incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} + - name: Notify new issue + uses: ecmwf/notify-teams-issue@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.github/workflows/notify-new-pr.yml b/.github/workflows/notify-new-pr.yml index af5dd82..2c22eba 100644 --- a/.github/workflows/notify-new-pr.yml +++ b/.github/workflows/notify-new-pr.yml @@ -2,12 +2,12 @@ name: Notify new PR on: pull_request_target: types: - - "opened" + - "opened" jobs: notify: runs-on: ubuntu-latest steps: - - name: Notify new PR - uses: ecmwf/notify-teams-pr@v1 - with: - incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} + - name: Notify new PR + uses: ecmwf/notify-teams-pr@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 986d81b..86aac40 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,54 +1,41 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace # Trailing whitespace checker - - id: end-of-file-fixer # Ensure files end in a newline - - id: check-json - - id: check-yaml # Check YAML files for syntax errors only - args: [--unsafe, --allow-multiple-documents] - - id: check-toml - # - id: check-added-large-files - - id: debug-statements # Check for debugger imports and py37+ breakpoint() - - id: mixed-line-ending - - id: no-commit-to-branch # Prevent committing to main / master - - id: check-merge-conflict # Check for files that contain merge conflict - exclude: /README\.rst$|^docs/.*\.rst$ - - repo: https://github.com/psf/black - rev: 24.8.0 - hooks: - - id: black - args: [--line-length=120] - - repo: https://github.com/keewis/blackdoc - rev: v0.3.8 - hooks: - - id: blackdoc - additional_dependencies: [black==23.3.0] - exclude: xr_engine_profile_rst\.py - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 - hooks: - - id: ruff - exclude: '(dev/.*|.*_)\.py$' - args: - - --line-length=120 - - --fix - - --exit-non-zero-on-fix - - --preview - - repo: https://github.com/executablebooks/mdformat - rev: 0.7.14 - hooks: - - id: mdformat - exclude: cruft-update-template.md - - repo: https://github.com/google/yamlfmt - rev: v0.13.0 - hooks: - - id: yamlfmt - - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 - hooks: - - id: sphinx-lint - - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.5.0" - hooks: - - id: pyproject-fmt +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace # Trailing whitespace checker + - id: end-of-file-fixer # Ensure files end in a newline + - id: check-json + - id: check-yaml # Check YAML files for syntax errors only + args: [--unsafe, --allow-multiple-documents] + - id: check-toml + # - id: check-added-large-files + - id: debug-statements # Check for debugger imports and py37+ breakpoint() + - id: mixed-line-ending + - id: no-commit-to-branch # Prevent committing to main / master + - id: check-merge-conflict # Check for files that contain merge conflict + exclude: /README\.rst$|^docs/.*\.rst$ +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.4 + hooks: + - id: ruff-check + exclude: '(dev/.*|.*_)\.py$' + args: + - --fix + - --exit-non-zero-on-fix + - id: ruff-format +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.14 + hooks: + - id: mdformat + exclude: cruft-update-template.md +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.16.0 + hooks: + - id: pretty-format-yaml + args: [--autofix, --preserve-quotes] + - id: pretty-format-toml + args: [--autofix] +- repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v1.0.0 + hooks: + - id: sphinx-lint diff --git a/.readthedocs.yml b/.readthedocs.yml index 5a61cc6..56a1ea1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,6 +6,6 @@ build: python: "3.9" python: install: - - requirements: docs/requirements.txt + - requirements: docs/requirements.txt sphinx: configuration: docs/conf.py diff --git a/docs/examples/rotate.ipynb b/docs/examples/rotate.ipynb index 8b34515..5468ac8 100644 --- a/docs/examples/rotate.ipynb +++ b/docs/examples/rotate.ipynb @@ -61,7 +61,7 @@ " import numpy as np\n", " from mpl_toolkits.basemap import Basemap\n", "\n", - " map = Basemap(projection='ortho', lat_0=lat_0, lon_0=lon_0, resolution='c')\n", + " map = Basemap(projection=\"ortho\", lat_0=lat_0, lon_0=lon_0, resolution=\"c\")\n", " map.drawcoastlines(linewidth=0.25)\n", " map.fillcontinents(color=(0.93, 0.93, 0.93), lake_color=(0.93, 0.93, 0.93))\n", " map.drawmeridians(np.arange(0, 360, 10), color=\"gray\")\n", diff --git a/environment.yml b/environment.yml index 4f9b2dd..cc997de 100644 --- a/environment.yml +++ b/environment.yml @@ -1,22 +1,22 @@ name: earthkit-geo channels: - - conda-forge - - nodefaults +- conda-forge +- nodefaults dependencies: - - pyproj - - cartopy - - shapely - - scipy - - requests - - make - - mypy - - myst-parser - - pre-commit - - pytest - - pytest-cov - - sphinx>=7.2.6 - - pip: - - sphinx-autoapi>=3.0.0 - - sphinx_rtd_theme - - sphinxcontrib-apidoc - - nbsphinx +- pyproj +- cartopy +- shapely +- scipy +- requests +- make +- mypy +- myst-parser +- pre-commit +- pytest +- pytest-cov +- sphinx>=7.2.6 +- pip: + - sphinx-autoapi>=3.0.0 +- sphinx_rtd_theme +- sphinxcontrib-apidoc +- nbsphinx diff --git a/pyproject.toml b/pyproject.toml index ee34b4b..41aaffa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,10 @@ [build-system] -requires = [ "setuptools>=61", "setuptools-scm>=8" ] +requires = ["setuptools>=61", "setuptools-scm>=8"] [project] -name = "earthkit-geo" -description = "Geospatial computations" -readme = "README.md" -license = { text = "Apache License Version 2.0" } authors = [ - { name = "European Centre for Medium-Range Weather Forecasts (ECMWF)", email = "software.support@ecmwf.int" }, + {name = "European Centre for Medium-Range Weather Forecasts (ECMWF)", email = "software.support@ecmwf.int"} ] -requires-python = ">=3.8" - classifiers = [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", @@ -24,41 +18,66 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: Implementation :: PyPy" ] -dynamic = [ "version" ] dependencies = [ "pyproj", "requests", - "scipy", + "scipy" ] -optional-dependencies.all = [ "cartopy" ] -optional-dependencies.cartography = [ "cartopy" ] +description = "Geospatial computations" +dynamic = ["version"] +license = {text = "Apache License Version 2.0"} +name = "earthkit-geo" +readme = "README.md" +requires-python = ">=3.8" +optional-dependencies.all = ["cartopy"] +optional-dependencies.cartography = ["cartopy"] optional-dependencies.test = [ "pytest", - "pytest-cov", + "pytest-cov" ] urls.Documentation = "https://earthkit-geo.readthedocs.io/" urls.Homepage = "https://github.com/ecmwf/earthkit-geo/" urls.Issues = "https://github.com/ecmwf/earthkit-geo/issues" urls.Repository = "https://github.com/ecmwf/earthkit-geo/" -[tool.setuptools.packages.find] -include = [ "earthkit.geo" ] -where = [ "src/" ] +[tool.coverage.run] +branch = "true" -[tool.setuptools_scm] -version_file = "src/earthkit/geo/_version.py" +[tool.pydocstyle] +add_ignore = ["D1", "D200", "D205", "D400", "D401"] +convention = "numpy" [tool.ruff] line-length = 120 +preview = true +lint.ignore = [ + "D1", # pydocstyle: Missing Docstrings + "D107", # pydocstyle: numpy convention + "D203", + "D205", + "D212", + "D213", + "D400", + "D401", + "D402", + "D413", + "D415", + "D416", + "D417" +] +lint.select = [ + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "W" # pycodestyle warnings +] -lint.select = [ "E", "F", "I" ] -lint.isort.force-single-line = true - -[tool.coverage.run] -branch = "true" +[tool.setuptools.packages.find] +include = ["earthkit.geo"] +where = ["src/"] -[tool.pydocstyle] -add_ignore = [ "D1", "D200", "D205", "D400", "D401" ] -convention = "numpy" +[tool.setuptools_scm] +version_file = "src/earthkit/geo/_version.py" diff --git a/src/earthkit/geo/__init__.py b/src/earthkit/geo/__init__.py index fa205cb..a8a0576 100644 --- a/src/earthkit/geo/__init__.py +++ b/src/earthkit/geo/__init__.py @@ -16,12 +16,8 @@ __version__ = "999" -from earthkit.geo.distance import GeoKDTree -from earthkit.geo.distance import haversine_distance -from earthkit.geo.distance import nearest_point_haversine -from earthkit.geo.distance import nearest_point_kdtree -from earthkit.geo.figure import IFS_SPHERE -from earthkit.geo.figure import UNIT_SPHERE +from earthkit.geo.distance import GeoKDTree, haversine_distance, nearest_point_haversine, nearest_point_kdtree +from earthkit.geo.figure import IFS_SPHERE, UNIT_SPHERE __all__ = [ "GeoKDTree", diff --git a/src/earthkit/geo/cartography.py b/src/earthkit/geo/cartography.py index b99f9b7..a8aa28c 100644 --- a/src/earthkit/geo/cartography.py +++ b/src/earthkit/geo/cartography.py @@ -119,7 +119,7 @@ def country_polygons(country_names, resolution=110e6): if name not in [record.attributes.get(attribute).lower() for record in reader.records()] ] if missing_countries: - raise ValueError(f"No countries or states named {missing_countries} found in Natural " "Earth's shapefiles") + raise ValueError(f"No countries or states named {missing_countries} found in Natural Earth's shapefiles") combined_geometry = unary_union(geometries) diff --git a/src/earthkit/geo/constants.py b/src/earthkit/geo/constants.py index 1772329..7515e82 100644 --- a/src/earthkit/geo/constants.py +++ b/src/earthkit/geo/constants.py @@ -7,9 +7,7 @@ # nor does it submit to any jurisdiction. # -""" -Collection of constants in SI units. -""" +"""Collection of constants in SI units.""" NORTH_POLE_LAT = 90 r"""Latitude of the north pole in degrees""" diff --git a/src/earthkit/geo/distance.py b/src/earthkit/geo/distance.py index 0d587af..4746282 100644 --- a/src/earthkit/geo/distance.py +++ b/src/earthkit/geo/distance.py @@ -11,8 +11,7 @@ from . import constants from .coord import latlon_to_xyz -from .figure import IFS_SPHERE -from .figure import UNIT_SPHERE +from .figure import IFS_SPHERE, UNIT_SPHERE def _regulate_lat(lat): diff --git a/src/earthkit/geo/figure.py b/src/earthkit/geo/figure.py index 728071a..7bfe626 100644 --- a/src/earthkit/geo/figure.py +++ b/src/earthkit/geo/figure.py @@ -9,7 +9,7 @@ class Figure: - """Figure representing the size and shape of a spheroid""" + """Figure representing the size and shape of a spheroid.""" pass diff --git a/src/earthkit/geo/gisco.py b/src/earthkit/geo/gisco.py index 744988f..2411110 100644 --- a/src/earthkit/geo/gisco.py +++ b/src/earthkit/geo/gisco.py @@ -22,7 +22,7 @@ "points": "LB", } -GISCO_URL_TEMPLATE = "https://gisco-services.ec.europa.eu" "/distribution/v2/{category}/shp" +GISCO_URL_TEMPLATE = "https://gisco-services.ec.europa.eu/distribution/v2/{category}/shp" AVAILABLE_YEARS = { "nuts": [2024, 2021, 2016, 2013, 2010, 2006, 2003], @@ -67,7 +67,7 @@ def _download_and_cache_gisco(name, category, geometry_type, resolution, suffix= geometry_type = GISCO_GEOMETRY_TYPES[geometry_type] except KeyError: raise ValueError( - f"Invalid geometry type '{geometry_type}'. " f"Valid types are: {list(GISCO_GEOMETRY_TYPES.keys())}" + f"Invalid geometry type '{geometry_type}'. Valid types are: {list(GISCO_GEOMETRY_TYPES.keys())}" ) # Validate year diff --git a/src/earthkit/geo/rotate.py b/src/earthkit/geo/rotate.py index 351e15c..7caf705 100644 --- a/src/earthkit/geo/rotate.py +++ b/src/earthkit/geo/rotate.py @@ -8,9 +8,7 @@ # from . import constants -from .coord import _normalise_lon -from .coord import latlon_to_xyz -from .coord import xyz_to_latlon +from .coord import _normalise_lon, latlon_to_xyz, xyz_to_latlon def _normalise(x): @@ -18,9 +16,7 @@ def _normalise(x): def _rotation_matrix(south_pole_lat, south_pole_lon): - """ - Define matrix for spherical rotation. - """ + """Define matrix for spherical rotation.""" import numpy as np theta = -np.deg2rad(south_pole_lat - constants.SOUTH_POLE_LAT) diff --git a/tests/environment-unit-tests.yml b/tests/environment-unit-tests.yml index 4ff7442..e17ad5e 100644 --- a/tests/environment-unit-tests.yml +++ b/tests/environment-unit-tests.yml @@ -1,23 +1,23 @@ name: earthkit-data channels: - - conda-forge - - nodefaults +- conda-forge +- nodefaults dependencies: - - pyproj - - scipy - - requests - - make - - myst-parser - - pre-commit - - pydata-sphinx-theme - - pytest - - pytest-cov - - pytest-forked - - pytest-timeout - - sphinx>=7.3.7 - - sphinx-autoapi - - sphinx_rtd_theme - - sphinxcontrib-apidoc - - nbsphinx - - sphinx-issues - - sphinx-copybutton +- pyproj +- scipy +- requests +- make +- myst-parser +- pre-commit +- pydata-sphinx-theme +- pytest +- pytest-cov +- pytest-forked +- pytest-timeout +- sphinx>=7.3.7 +- sphinx-autoapi +- sphinx_rtd_theme +- sphinxcontrib-apidoc +- nbsphinx +- sphinx-issues +- sphinx-copybutton diff --git a/tests/test_cartography.py b/tests/test_cartography.py index 21fe965..916f1d9 100644 --- a/tests/test_cartography.py +++ b/tests/test_cartography.py @@ -9,12 +9,9 @@ import pytest -from shapely.geometry import MultiPolygon -from shapely.geometry import Polygon +from shapely.geometry import MultiPolygon, Polygon -from earthkit.geo.cartography import _closest_resolution -from earthkit.geo.cartography import country_polygons -from earthkit.geo.cartography import multipolygon_to_coordinates +from earthkit.geo.cartography import _closest_resolution, country_polygons, multipolygon_to_coordinates @pytest.mark.parametrize( diff --git a/tests/test_coord.py b/tests/test_coord.py index 88978ee..cba6104 100644 --- a/tests/test_coord.py +++ b/tests/test_coord.py @@ -12,8 +12,7 @@ import numpy as np import pytest -from earthkit.geo.coord import latlon_to_xyz -from earthkit.geo.coord import xyz_to_latlon +from earthkit.geo.coord import latlon_to_xyz, xyz_to_latlon from .testing import normalise_lon diff --git a/tests/test_distance.py b/tests/test_distance.py index 491c185..1f6fa84 100644 --- a/tests/test_distance.py +++ b/tests/test_distance.py @@ -12,10 +12,7 @@ import numpy as np import pytest -from earthkit.geo import UNIT_SPHERE -from earthkit.geo import haversine_distance -from earthkit.geo import nearest_point_haversine -from earthkit.geo import nearest_point_kdtree +from earthkit.geo import UNIT_SPHERE, haversine_distance, nearest_point_haversine, nearest_point_kdtree def get_nearest_method(mode): @@ -110,7 +107,7 @@ def test_haversine_distance_with_radius(): # lat is out of range -@pytest.mark.parametrize("p_ref", [((90.00001, 0)), (((-90.00001, 0)))]) +@pytest.mark.parametrize("p_ref", [((90.00001, 0)), ((-90.00001, 0))]) def test_haversine_distance_invalid(p_ref): p = (-48, 12) d = haversine_distance(p_ref, p) diff --git a/tests/test_rotate.py b/tests/test_rotate.py index 1b9246d..69189a8 100644 --- a/tests/test_rotate.py +++ b/tests/test_rotate.py @@ -12,13 +12,9 @@ import numpy as np import pytest -from earthkit.geo.rotate import rotate -from earthkit.geo.rotate import rotate_vector -from earthkit.geo.rotate import unrotate -from earthkit.geo.rotate import unrotate_vector +from earthkit.geo.rotate import rotate, rotate_vector, unrotate, unrotate_vector -from .testing import earthkit_test_data_file -from .testing import normalise_lon +from .testing import earthkit_test_data_file, normalise_lon def _make_proj(name):