Skip to content

Commit

Permalink
Pull request #18: IP-241 implement pre commit
Browse files Browse the repository at this point in the history
Merge in SITC/harmony-regridding-service from IP-241-implement-pre-commit to main

* commit '48d218cb9592f5ee844d6760d3657956f6123dad':
  IP-241 - Increment service for black formatting changes.
  IP-241 - Add .git-blame-ignore-revs.
  IP-241 - Implement black code formatting across repository.
  IP-241 - Implement pre-commit.
  • Loading branch information
owenlittlejohns committed Apr 10, 2024
2 parents d88534b + 48d218c commit c30235e
Show file tree
Hide file tree
Showing 19 changed files with 1,105 additions and 770 deletions.
5 changes: 5 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# For more information, see:
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view

# Black code formatting of entire repository
2967ed10ef6e38d4346735c1693212a56c756902
20 changes: 20 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-json
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.3.0
hooks:
- id: black-jupyter
args: ["--skip-string-normalization"]
language_version: python3.11
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## v0.0.4
### 2024-04-10

This version of the Harmony Regridding service implements `black` code
formatting across the entire repository. There should be no functional changes
to the service.

## v0.0.3
### 2023-12-18

Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ CI/CD pipeline. In future, this project will be migrated from Bitbucket to
GitHub, at which point the CI/CD will be migrated to workflows that use GitHub
Actions.

## pre-commit hooks:

This repository uses [pre-commit](https://pre-commit.com/) to enable pre-commit
checking the repository for some coding standard best practices. These include:

* Removing trailing whitespaces.
* Removing blank lines at the end of a file.
* JSON files have valid formats.
* [ruff](https://github.com/astral-sh/ruff) Python linting checks.
* [black](https://black.readthedocs.io/en/stable/index.html) Python code
formatting checks.

To enable these checks:

```bash
# Install pre-commit Python package as part of test requirements:
pip install -r tests/pip_test_requirements.txt

# Install the git hook scripts:
pre-commit install

# (Optional) Run against all files:
pre-commit run --all-files
```

When you try to make a new commit locally, `pre-commit` will automatically run.
If any of the hooks detect non-compliance (e.g., trailing whitespace), that
hook will state it failed, and also try to fix the issue. You will need to
review and `git add` the changes before you can make a commit.

It is planned to implement additional hooks, possibly including tools such as
`mypy`.

## Versioning:

Service Docker images for the Harmony Regridding Service adhere to semantic
Expand Down
2 changes: 1 addition & 1 deletion docker/service_version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.3
0.0.4
2 changes: 1 addition & 1 deletion docker/tests.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ENV PYTHONDONTWRITEBYTECODE=1
COPY tests/pip_test_requirements.txt .
RUN pip install --no-input -r pip_test_requirements.txt

# Copy test directory containing Python unittest suite, test data and utilities
# Copy test directory containing Python unittest suite, test data and utilities
COPY ./tests tests

# Configure a container to be executable via the `docker run` command.
Expand Down
10 changes: 6 additions & 4 deletions harmony_regridding_service/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Run the Harmony Regridding Service Adapter via the Harmony CLI. """

from argparse import ArgumentParser
from sys import argv
from typing import List
Expand All @@ -9,12 +10,13 @@


def main(arguments: List[str]):
""" Parse command line arguments and invoke the appropriate method to
respond to them
"""Parse command line arguments and invoke the appropriate method to
respond to them
"""
parser = ArgumentParser(prog='harmony-regridding-service',
description='Run Harmony regridding service.')
parser = ArgumentParser(
prog='harmony-regridding-service', description='Run Harmony regridding service.'
)

setup_cli(parser)
harmony_arguments, _ = parser.parse_known_args(arguments[1:])
Expand Down
133 changes: 79 additions & 54 deletions harmony_regridding_service/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
another grid as specified in the input Harmony message.
"""

from os.path import basename
from shutil import rmtree
from tempfile import mkdtemp
Expand All @@ -13,49 +14,53 @@
from harmony import BaseHarmonyAdapter
from harmony.message import Source as HarmonySource
from harmony.message_utility import has_self_consistent_grid
from harmony.util import (bbox_to_geometry, download, generate_output_filename,
stage)
from harmony.util import bbox_to_geometry, download, generate_output_filename, stage
from pystac import Asset, Catalog, Item

from harmony_regridding_service.exceptions import (InvalidInterpolationMethod,
InvalidTargetCRS,
InvalidTargetGrid)
from harmony_regridding_service.exceptions import (
InvalidInterpolationMethod,
InvalidTargetCRS,
InvalidTargetGrid,
)
from harmony_regridding_service.regridding_service import regrid
from harmony_regridding_service.utilities import (get_file_mime_type,
has_valid_crs,
has_valid_interpolation)
from harmony_regridding_service.utilities import (
get_file_mime_type,
has_valid_crs,
has_valid_interpolation,
)


class RegriddingServiceAdapter(BaseHarmonyAdapter):
""" This class extends the BaseHarmonyAdapter class from the
harmony-service-lib package to implement regridding operations.
"""This class extends the BaseHarmonyAdapter class from the
harmony-service-lib package to implement regridding operations.
"""

def __init__(self, message, catalog=None, config=None):
super().__init__(message, catalog=catalog, config=config)
self.cache = {'grids': {}}

def invoke(self) -> Catalog:
""" Adds validation to process_item based invocations. """
"""Adds validation to process_item based invocations."""
self.validate_message()
return super().invoke()

def validate_message(self):
""" Validates that the contents of the Harmony message provides all
necessary parameters.
For an input Harmony message to be considered valid it must:
* Contain a valid target grid, with `format.scaleExtent` and either
`format.scaleSize` or both `format.height` and `format.width`
fully populated.
* Not specify an incompatible target CRS. Initially, the target CRS
is limited to geographic. The message should either specify a
geographic CRS, or not specify one at all.
* Not specify an incompatible interpolation method. Initially, the
Harmony Regridding Service will use Elliptical Weighted Averaging
to interpolate when needed. The message should either specify
this interpolation method, or not specify one at all.
"""Validates that the contents of the Harmony message provides all
necessary parameters.
For an input Harmony message to be considered valid it must:
* Contain a valid target grid, with `format.scaleExtent` and either
`format.scaleSize` or both `format.height` and `format.width`
fully populated.
* Not specify an incompatible target CRS. Initially, the target CRS
is limited to geographic. The message should either specify a
geographic CRS, or not specify one at all.
* Not specify an incompatible interpolation method. Initially, the
Harmony Regridding Service will use Elliptical Weighted Averaging
to interpolate when needed. The message should either specify
this interpolation method, or not specify one at all.
"""
if not has_valid_crs(self.message):
Expand All @@ -68,54 +73,72 @@ def validate_message(self):
raise InvalidTargetGrid()

def process_item(self, item: Item, source: HarmonySource) -> Item:
""" Processes a single input STAC item. """
"""Processes a single input STAC item."""
try:
working_directory = mkdtemp()
results = item.clone()
results.assets = {}

asset = next((item_asset for item_asset in item.assets.values()
if 'data' in (item_asset.roles or [])))
asset = next(
(
item_asset
for item_asset in item.assets.values()
if 'data' in (item_asset.roles or [])
)
)

# Download the input:
input_filepath = download(asset.href, working_directory,
logger=self.logger, cfg=self.config,
access_token=self.message.accessToken)
input_filepath = download(
asset.href,
working_directory,
logger=self.logger,
cfg=self.config,
access_token=self.message.accessToken,
)

transformed_file_name = regrid(self, input_filepath, source)

# Stage the transformed output:
transformed_mime_type = get_file_mime_type(transformed_file_name)
staged_url = self.stage_output(transformed_file_name, asset.href,
transformed_mime_type)
staged_url = self.stage_output(
transformed_file_name, asset.href, transformed_mime_type
)

return self.create_output_stac_item(item, staged_url,
transformed_mime_type)
return self.create_output_stac_item(item, staged_url, transformed_mime_type)
except Exception as exception:
self.logger.exception(exception)
raise exception
finally:
rmtree(working_directory)

def stage_output(self, transformed_file: str, input_file: str,
transformed_mime_type: Optional[str]) -> str:
""" Generate an output file name based on the input asset URL and the
operations performed to produce the output. Use this name to stage
the output in the S3 location specified in the input Harmony
message.
def stage_output(
self,
transformed_file: str,
input_file: str,
transformed_mime_type: Optional[str],
) -> str:
"""Generate an output file name based on the input asset URL and the
operations performed to produce the output. Use this name to stage
the output in the S3 location specified in the input Harmony
message.
"""
output_file_name = generate_output_filename(input_file,
is_regridded=True)

return stage(transformed_file, output_file_name, transformed_mime_type,
location=self.message.stagingLocation,
logger=self.logger, cfg=self.config)
output_file_name = generate_output_filename(input_file, is_regridded=True)

return stage(
transformed_file,
output_file_name,
transformed_mime_type,
location=self.message.stagingLocation,
logger=self.logger,
cfg=self.config,
)

def create_output_stac_item(self, input_stac_item: Item, staged_url: str,
transformed_mime_type: str) -> Item:
""" Create an output STAC item used to access the transformed and
staged output in S3.
def create_output_stac_item(
self, input_stac_item: Item, staged_url: str, transformed_mime_type: str
) -> Item:
"""Create an output STAC item used to access the transformed and
staged output in S3.
"""
output_stac_item = input_stac_item.clone()
Expand All @@ -127,8 +150,10 @@ def create_output_stac_item(self, input_stac_item: Item, staged_url: str,
output_stac_item.geometry = bbox_to_geometry(output_stac_item.bbox)

output_stac_item.assets['data'] = Asset(
staged_url, title=basename(staged_url),
media_type=transformed_mime_type, roles=['data']
staged_url,
title=basename(staged_url),
media_type=transformed_mime_type,
roles=['data'],
)

return output_stac_item
23 changes: 15 additions & 8 deletions harmony_regridding_service/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,45 @@
application.
"""

from harmony.util import HarmonyException


class RegridderException(HarmonyException):
""" Base service exception. """
"""Base service exception."""

def __init__(self, message=None):
super().__init__(message, 'sds/harmony-regridder')


class InvalidTargetCRS(RegridderException):
""" Raised when a request specifies an unsupported target Coordinate
Reference System.
"""Raised when a request specifies an unsupported target Coordinate
Reference System.
"""

def __init__(self, target_crs: str):
super().__init__(f'Target CRS not supported: "{target_crs}"')


class InvalidInterpolationMethod(RegridderException):
""" Raised when a user specifies an unsupported interpolation method. """
"""Raised when a user specifies an unsupported interpolation method."""

def __init__(self, interpolation_method: str):
super().__init__('Interpolation method not supported: '
f'"{interpolation_method}"')
super().__init__(
'Interpolation method not supported: ' f'"{interpolation_method}"'
)


class InvalidTargetGrid(RegridderException):
""" Raised when a request specifies an incomplete or invalid grid. """
"""Raised when a request specifies an incomplete or invalid grid."""

def __init__(self):
super().__init__('Insufficient or invalid target grid parameters.')


class InvalidSourceDimensions(RegridderException):
""" Raised when a source granule does not meet the expected dimension shapes. """
"""Raised when a source granule does not meet the expected dimension shapes."""

def __init__(self, message: str):
super().__init__(message)
Loading

0 comments on commit c30235e

Please sign in to comment.