Skip to content

Commit

Permalink
Merge pull request #17 from asfadmin/rew/metaspec-builder
Browse files Browse the repository at this point in the history
DOI 2.22.23 Add metaspec builder functions
  • Loading branch information
reweeden authored May 20, 2024
2 parents 5ec525a + f941531 commit e31dbea
Show file tree
Hide file tree
Showing 26 changed files with 1,841 additions and 955 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
- uses: TrueBrain/actions-flake8@v2
with:
flake8_version: 6.0.0
plugins: flake8-isort==6.0.0 flake8-quotes==3.3.2
plugins: flake8-isort==6.1.1 flake8-quotes==3.4.0
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
fail-fast: false

steps:
Expand Down
239 changes: 239 additions & 0 deletions mandible/metadata_mapper/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Callable, Dict, Type, TypeVar

from .directive import (
Add,
FloorDiv,
Mapped,
Mul,
Reformatted,
Sub,
TemplateDirective,
TrueDiv,
)
from .types import Key, Template

# For testing purposes to ensure we implement builders for all directives
_DIRECTIVE_BUILDER_REGISTRY: Dict[str, Callable[..., "DirectiveBuilder"]] = {}


@dataclass
class BuildConfig:
directive_marker: str


class Builder(ABC):
@abstractmethod
def build(self, config: BuildConfig) -> Template:
pass


class DirectiveBuilder(Builder):
def __init__(
self,
name: str,
params: Dict[str, Any],
):
self.name = name
self.params = params

def build(self, config: BuildConfig) -> Template:
return {
f"{config.directive_marker}{self.name}": {
k: v.build(config) if isinstance(v, Builder) else v
for k, v in self.params.items()
},
}

def __add__(self, other: Any) -> "DirectiveBuilder":
return add(self, other)

def __radd__(self, other: Any) -> "DirectiveBuilder":
return add(other, self)

def __floordiv__(self, other: Any) -> "DirectiveBuilder":
return floordiv(self, other)

def __rfloordiv__(self, other: Any) -> "DirectiveBuilder":
return floordiv(other, self)

def __mul__(self, other: Any) -> "DirectiveBuilder":
return mul(self, other)

def __rmul__(self, other: Any) -> "DirectiveBuilder":
return mul(other, self)

def __sub__(self, other: Any) -> "DirectiveBuilder":
return sub(self, other)

def __rsub__(self, other: Any) -> "DirectiveBuilder":
return sub(other, self)

def __truediv__(self, other: Any) -> "DirectiveBuilder":
return truediv(self, other)

def __rtruediv__(self, other: Any) -> "DirectiveBuilder":
return truediv(other, self)


T = TypeVar("T")


def _directive_builder(directive: Type["TemplateDirective"]) -> Callable[[T], T]:
directive_name = directive.directive_name
assert directive_name is not None

def decorator(func):
func.__doc__ = directive.__doc__

_DIRECTIVE_BUILDER_REGISTRY[directive_name] = func
return func

return decorator


@_directive_builder(Mapped)
def mapped(
source: str,
key: Key,
) -> DirectiveBuilder:
directive_name = Mapped.directive_name
assert directive_name is not None

return DirectiveBuilder(
directive_name,
{
"source": source,
"key": key,
},
)


@_directive_builder(Reformatted)
def reformatted(
format: str,
value: Any,
key: Key,
) -> DirectiveBuilder:
directive_name = Reformatted.directive_name
assert directive_name is not None

return DirectiveBuilder(
directive_name,
{
"format": format,
"value": value,
"key": key,
},
)

#
# Operations
#


def _binop_directive(directive_name: str, left: Any, right: Any):
return DirectiveBuilder(
directive_name,
{
"left": left,
"right": right,
},
)


@_directive_builder(Add)
def add(
left: Any,
right: Any,
) -> DirectiveBuilder:
directive_name = Add.directive_name
assert directive_name is not None

return _binop_directive(directive_name, left, right)


@_directive_builder(FloorDiv)
def floordiv(
left: Any,
right: Any,
) -> DirectiveBuilder:
directive_name = FloorDiv.directive_name
assert directive_name is not None

return _binop_directive(directive_name, left, right)


@_directive_builder(Mul)
def mul(
left: Any,
right: Any,
) -> DirectiveBuilder:
directive_name = Mul.directive_name
assert directive_name is not None

return _binop_directive(directive_name, left, right)


@_directive_builder(Sub)
def sub(
left: Any,
right: Any,
) -> DirectiveBuilder:
directive_name = Sub.directive_name
assert directive_name is not None

return _binop_directive(directive_name, left, right)


@_directive_builder(TrueDiv)
def truediv(
left: Any,
right: Any,
) -> DirectiveBuilder:
directive_name = TrueDiv.directive_name
assert directive_name is not None

return _binop_directive(directive_name, left, right)


def build(template: Any, directive_marker: str = "@") -> Template:
"""Convert a template created with builder classes to a standard template
that is ready to be used with the metadata mapper.
When using builder classes, you must convert your template using `build`.
:param template: template with possible `Builder` values
:param directive_marker: marker to use for identifying directives
:returns: Template - a standard template with all `Builder`s replaced
"""
config = BuildConfig(directive_marker=directive_marker)

return build_with_config(template, config)


def build_with_config(template: Any, config: BuildConfig) -> Template:
"""Same as build but takes configuration options as a BuildConfig object.
:param template: template with possible `Builder` values
:param config: BuildConfig object to customize generation
:returns: Template - a standard template with all `Builder`s replaced
"""
if isinstance(template, dict):
return {
k: build_with_config(v, config)
for k, v in template.items()
}
elif isinstance(template, list):
return [build_with_config(v, config) for v in template]
elif isinstance(template, Builder):
return template.build(config)
elif isinstance(template, tuple):
return tuple(build_with_config(v, config) for v in template)
elif isinstance(template, set):
return set(build_with_config(v, config) for v in template)
elif isinstance(template, (str, int, float, bool)):
return template

raise ValueError(template)
105 changes: 0 additions & 105 deletions mandible/metadata_mapper/directive.py

This file was deleted.

18 changes: 18 additions & 0 deletions mandible/metadata_mapper/directive/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .directive import DIRECTIVE_REGISTRY, Key, TemplateDirective, get_key
from .mapped import Mapped
from .operations import Add, FloorDiv, Mul, Sub, TrueDiv
from .reformatted import Reformatted

__all__ = (
"Add",
"DIRECTIVE_REGISTRY",
"FloorDiv",
"Key",
"Mapped",
"Mul",
"Reformatted",
"Sub",
"TemplateDirective",
"TrueDiv",
"get_key",
)
Loading

0 comments on commit e31dbea

Please sign in to comment.