123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- import click
- import marshmallow as ma
- from click import get_current_context
- from flask.cli import with_appcontext as with_cli_appcontext
- from pint import DefinitionSyntaxError, DimensionalityError, UndefinedUnitError
- from flexmeasures.utils.unit_utils import to_preferred, ur
- class MarshmallowClickMixin(click.ParamType):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.name = self.__class__.__name__
- def get_metavar(self, param, **kwargs):
- return self.__class__.__name__
- def convert(self, value, param, ctx, **kwargs):
- try:
- return self.deserialize(value, **kwargs)
- except ma.exceptions.ValidationError as e:
- raise click.exceptions.BadParameter(e, ctx=ctx, param=param)
- class FMValidationError(ma.exceptions.ValidationError):
- """
- Custom validation error class.
- It differs from the classic validation error by having two
- attributes, according to the USEF 2015 reference implementation.
- Subclasses of this error might adjust the `status` attribute accordingly.
- """
- result = "Rejected"
- status = "UNPROCESSABLE_ENTITY"
- def with_appcontext_if_needed():
- """Execute within the script's application context, in case there is one.
- An exception is `flexmeasures run`, which has a click context at the time the decorator is called,
- but no longer has a click context at the time the decorated function is called,
- which, typically, is a request to the running FlexMeasures server.
- """
- def decorator(f):
- ctx = get_current_context(silent=True)
- if ctx and not ctx.invoked_subcommand == "run":
- return with_cli_appcontext(f)
- return f
- return decorator
- def convert_to_quantity(value: str, to_unit: str) -> ur.Quantity:
- """Convert value to quantity in the given unit.
- :param value: Value to convert.
- :param to_unit: Unit to convert to. If the unit starts with a '/',
- the value can have any unit, and the unit is used as the denominator.
- :returns: Quantity in the desired unit.
- """
- if to_unit.startswith("/") and len(to_unit) < 2:
- raise ValueError(f"Variable `to_unit='{to_unit}'` must define a denominator.")
- try:
- if to_unit.startswith("/"):
- return to_preferred(
- ur.Quantity(value) * ur.Quantity(to_unit[1:])
- ) / ur.Quantity(to_unit[1:])
- return ur.Quantity(value).to(ur.Quantity(to_unit))
- except DimensionalityError as e:
- raise FMValidationError(f"Cannot convert value `{value}` to '{to_unit}'") from e
- except (AssertionError, DefinitionSyntaxError, UndefinedUnitError) as e:
- raise FMValidationError(
- f"Cannot convert value '{value}' to a valid quantity. {e}"
- )
|