Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[flake8]
max-line-length = 120
extend-ignore = W503
# E203 incompatible with the ruff autoformatter
extend-ignore = W503, E203
inline-quotes = double
9 changes: 9 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ jobs:
flake8_version: 6.0.0
plugins: flake8-isort==6.1.1 flake8-quotes==3.4.0 flake8-commas==4.0.0

ruff-format:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v1
with:
args: format --check --diff

mypy:
runs-on: ubuntu-latest

Expand Down
1 change: 1 addition & 0 deletions mandible/internal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .types import Registry

__all__ = (
# ruff hint
"Registry",
)
4 changes: 2 additions & 2 deletions mandible/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def _get_dot_path(data: JsonValue, path: str) -> list[JsonValue]:

def _parse_dot_path(path: str) -> Generator[str]:
for part in path.split("."):
if (m := BRACKET_PATTERN.search(part)):
yield part[:m.start()]
if m := BRACKET_PATTERN.search(part):
yield part[: m.start()]
yield m.group(1)
else:
yield part
3 changes: 3 additions & 0 deletions mandible/metadata_mapper/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(
def build(self, config: BuildConfig) -> Template:
return {
f"{config.directive_marker}{self.name}": {
# ruff hint
k: v.build(config) if isinstance(v, Builder) else v
for k, v in self.params.items()
},
Expand Down Expand Up @@ -139,6 +140,7 @@ def reformatted(
params,
)


#
# Operations
#
Expand Down Expand Up @@ -237,6 +239,7 @@ def build_with_config(template: Any, config: BuildConfig) -> Template:
"""
if isinstance(template, dict):
return {
# ruff hint
k: build_with_config(v, config)
for k, v in template.items()
}
Expand Down
4 changes: 2 additions & 2 deletions mandible/metadata_mapper/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ def _replace_context_values(obj: Any, context_dict: dict) -> Any:
)
if len(result) > 1:
raise ContextValueError(
f"context path {repr(obj.path)} returned more than "
f"one value",
f"context path {repr(obj.path)} returned more than one value",
)

return result[0]

if isinstance(obj, dict):
return {
# ruff hint
k: _replace_context_values(v, context_dict)
for k, v in obj.items()
}
Expand Down
1 change: 1 addition & 0 deletions mandible/metadata_mapper/directive/directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TemplateDirective(ABC):
A directive is a special marker in the metadata template which will be
replaced by the MetadataMapper.
"""

# Registry boilerplate
def __init_subclass__(
cls,
Expand Down
3 changes: 1 addition & 2 deletions mandible/metadata_mapper/directive/reformatted.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ def call(self) -> Template:
value = self.value.encode()
else:
raise MetadataMapperError(
"value must be of type 'bytes' or 'str' but got "
f"{repr(type(self.value).__name__)}",
f"value must be of type 'bytes' or 'str' but got {repr(type(self.value).__name__)}",
)

return self.format_obj.get_value(
Expand Down
15 changes: 13 additions & 2 deletions mandible/metadata_mapper/format/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ def get_values(
"""Get a list of values from a file"""

with self.parse_data(file) as data:
return {key: self._eval_key_wrapper(data, key) for key in keys}
return {
# ruff hint
key: self._eval_key_wrapper(data, key)
for key in keys
}

def get_value(self, file: IO[bytes], key: Key) -> Any:
"""Convenience function for getting a single value"""
Expand Down Expand Up @@ -114,6 +118,7 @@ def eval_key(data: T, key: Key) -> Any:

# Define formats that don't require extra dependencies


@dataclass
class Json(FileFormat[JsonValue]):
"""A Format for querying Json files.
Expand Down Expand Up @@ -157,6 +162,7 @@ class ZipMember(Format):

def __post_init__(self) -> None:
self._compiled_filters = {
# ruff hint
k: re.compile(v) if isinstance(v, str) else v
for k, v in self.filters.items()
}
Expand Down Expand Up @@ -217,6 +223,7 @@ def _matches_filters(self, zipinfo: zipfile.ZipInfo) -> bool:


ZIP_INFO_ATTRS = [
# ruff hint
name
for name, _ in inspect.getmembers(zipfile.ZipInfo, inspect.isdatadescriptor)
if not name.startswith("_")
Expand All @@ -233,7 +240,11 @@ def parse_data(file: IO[bytes]) -> Generator[dict]:
with zipfile.ZipFile(file, "r") as zf:
yield {
"infolist": [
{k: getattr(info, k) for k in ZIP_INFO_ATTRS}
{
# ruff hint
k: getattr(info, k)
for k in ZIP_INFO_ATTRS
}
for info in zf.infolist()
],
"filename": zf.filename,
Expand Down
4 changes: 3 additions & 1 deletion mandible/metadata_mapper/format/h5.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def normalize(node_val: Any) -> Any:
return float(node_val)
if isinstance(node_val, np.ndarray):
value = [
x.decode("utf-8") if isinstance(x, bytes) else x for x in node_val.tolist()
# ruff hint
x.decode("utf-8") if isinstance(x, bytes) else x
for x in node_val.tolist()
]
return value
if isinstance(node_val, bytes):
Expand Down
4 changes: 2 additions & 2 deletions mandible/metadata_mapper/format/placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ class _PlaceholderBase(FileFormat[None], register=False):
"""Base class for defining placeholder implementations for classes that
require extra dependencies to be installed.
"""

def __init__(self, dep: str):
raise Exception(
f"{dep} must be installed to use the {self.__class__.__name__} "
"format class",
f"{dep} must be installed to use the {self.__class__.__name__} format class",
)

@staticmethod
Expand Down
21 changes: 9 additions & 12 deletions mandible/metadata_mapper/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ def get_metadata(self, context: Context) -> Template:
raise
except Exception as e:
raise MetadataMapperError(
f"failed to inject context values into source "
f"{repr(name)}: {e}",
f"failed to inject context values into source {repr(name)}: {e}",
) from e

try:
Expand Down Expand Up @@ -157,6 +156,7 @@ def _get_directive_name(
debug_path: str,
) -> Optional[tuple[str, dict[str, Template]]]:
directive_configs = [
# ruff hint
(k, v)
for (k, v) in value.items()
if k.startswith(self.directive_marker)
Expand All @@ -165,18 +165,17 @@ def _get_directive_name(
return None

if len(directive_configs) > 1:
directive_names = ", ".join(repr(k) for k, _ in directive_configs)
raise TemplateError(
"multiple directives found in config: "
f"{', '.join(repr(k) for k, v in directive_configs)}",
f"multiple directives found in config: {directive_names}",
debug_path,
)

directive_name, directive_config = directive_configs[0]

if not isinstance(directive_config, dict):
raise TemplateError(
"directive body should be type 'dict' not "
f"{repr(directive_config.__class__.__name__)}",
f"directive body should be type 'dict' not {repr(directive_config.__class__.__name__)}",
f"{debug_path}.{directive_name}",
)

Expand All @@ -190,7 +189,7 @@ def _get_directive(
config: dict[str, Template],
debug_path: str,
) -> TemplateDirective:
cls = DIRECTIVE_REGISTRY.get(directive_name[len(self.directive_marker):])
cls = DIRECTIVE_REGISTRY.get(directive_name[len(self.directive_marker) :])
if cls is None:
raise TemplateError(
f"invalid directive {repr(directive_name)}",
Expand All @@ -201,9 +200,7 @@ def _get_directive(

# Ignore the `self`, `context`, and `sources` parameters
required_keys = set(
argspec.args[3:-len(argspec.defaults)]
if argspec.defaults else
argspec.args[3:],
argspec.args[3 : -len(argspec.defaults)] if argspec.defaults else argspec.args[3:],
)
config_keys = set(config.keys())
diff = required_keys - config_keys
Expand All @@ -213,14 +210,14 @@ def _get_directive(
if len(diff) > 1:
s = "s"
raise TemplateError(
f"missing key{s}: "
f"{', '.join(repr(d) for d in sorted(diff))}",
f"missing key{s}: {', '.join(repr(d) for d in sorted(diff))}",
debug_path,
)

# For forward compatibility, ignore any unexpected keys
all_keys = set(argspec.args[2:])
kwargs = {
# ruff hint
k: v
for k, v in config.items()
if k in all_keys
Expand Down
6 changes: 4 additions & 2 deletions mandible/metadata_mapper/source_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, config: dict):

def get_sources(self) -> dict[str, Source]:
return {
# ruff hint
key: self._create_source(key, config)
for key, config in self.config.items()
}
Expand Down Expand Up @@ -85,8 +86,7 @@ def _create_object(

if not issubclass(cls, base_cls):
raise SourceProviderError(
f"invalid {key} type {repr(cls_name)} must be a subclass of "
f"{repr(base_cls.__name__)}",
f"invalid {key} type {repr(cls_name)} must be a subclass of {repr(base_cls.__name__)}",
)

return self._instantiate_class(cls, config)
Expand All @@ -111,6 +111,7 @@ def _get_class_from_registry(

def _instantiate_class(self, cls: type[T], config: dict[str, Any]) -> T:
kwargs = {
# ruff hint
k: self._convert_arg(cls, k, v)
for k, v in config.items()
if k != "class"
Expand All @@ -124,6 +125,7 @@ def _convert_arg(self, parent_cls: type[Any], key: str, arg: Any) -> Any:
return self._create_object(parent_cls, key, arg)

return {
# ruff hint
k: self._convert_arg(parent_cls, k, v)
for k, v in arg.items()
}
Expand Down
4 changes: 2 additions & 2 deletions mandible/metadata_mapper/storage/placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ class _PlaceholderBase(Storage, register=False):
Base class for defining placeholder implementations for classes that
require extra dependencies to be installed
"""

def __init__(self, dep: str):
raise Exception(
f"{dep} must be installed to use the {self.__class__.__name__} "
"format class",
f"{dep} must be installed to use the {self.__class__.__name__} format class",
)

def open_file(self, context: Context) -> IO[bytes]:
Expand Down
3 changes: 3 additions & 0 deletions mandible/metadata_mapper/storage/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def open_file(self, context: Context) -> IO[bytes]:

# Define storages that don't require extra dependencies


@dataclass
class Dummy(Storage):
"""A dummy storage that returns a hardcoded byte stream.
Expand All @@ -54,6 +55,7 @@ class FilteredStorage(Storage, register=False):
"""A storage which matches a set of filters on the context's files and
returns data from the matching file.
"""

# Begin class definition
filters: dict[str, Any] = field(default_factory=dict)

Expand All @@ -64,6 +66,7 @@ def __post_init__(self) -> None:
def _compiled_filters(self) -> dict[str, Any]:
if self._compiled_filters_cache is None:
self._compiled_filters_cache = {
# ruff hint
k: re.compile(v) if isinstance(v, str) else v
for k, v in self.filters.items()
}
Expand Down
11 changes: 6 additions & 5 deletions mandible/umm_classes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,10 @@ def __init__(self, granule: CMAGranule):
self.granule = granule

@overload
def date_to_str(self, date: datetime.date) -> str:
...
def date_to_str(self, date: datetime.date) -> str: ...

@overload
def date_to_str(self, date: None) -> None:
...
def date_to_str(self, date: None) -> None: ...

def date_to_str(self, date: Optional[datetime.date]) -> Optional[str]:
"""Serialize a datetime.date or datetime.datetime as a string using the
Expand Down Expand Up @@ -207,7 +205,8 @@ def get_ummg(self) -> Ummg:
additional_attributes=sorted(
self.get_additional_attributes(),
key=lambda attr: attr["Name"],
) or None,
)
or None,
cloud_cover=self.get_cloud_cover(),
collection_reference=self.get_collection_reference(),
data_granule=self.get_data_granule(),
Expand All @@ -218,6 +217,7 @@ def get_ummg(self) -> Ummg:
metadata_specification=self.get_metadata_specification(),
native_projection_names=self.get_native_projection_names() or None,
orbit_calculated_spatial_domains=(
# ruff hint
self.get_orbit_calculated_spatial_domains() or None
),
pge_version_class=self.get_pge_version_class(),
Expand Down Expand Up @@ -269,6 +269,7 @@ def get_archive_and_distribution_information(
def get_data_granule(self) -> DataGranule:
return data_granule(
archive_and_distribution_information=(
# ruff hint
self.get_archive_and_distribution_information() or None
),
day_night_flag=self.get_day_night_flag(),
Expand Down
3 changes: 1 addition & 2 deletions mandible/umm_classes/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ def spatial_extent(

if not obj:
raise ValueError(
"one of 'granule_localities', 'horizontal_spatial_domain', or "
"'vertical_spatial_domains' is required",
"one of 'granule_localities', 'horizontal_spatial_domain', or 'vertical_spatial_domains' is required",
)

return obj
Expand Down
Loading