Skip to content

Commit

Permalink
Implement new wiz.res() API (see #33)
Browse files Browse the repository at this point in the history
  • Loading branch information
Splines committed Apr 11, 2024
1 parent b0c7023 commit 3572e8f
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 94 deletions.
7 changes: 6 additions & 1 deletion src/api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ def _parse_uncertainty_value(value: Union[float, int, str, Decimal]) -> Value:
"""Parses the value of an uncertainty."""

if isinstance(value, str):
check_if_number_string(value)
try:
check_if_number_string(value)
except Exception as exc:
msg = error_messages.STRING_MUST_BE_NUMBER.format(value=value)
msg += ". " + error_messages.UNIT_NOT_PASSED_AS_KEYWORD_ARGUMENT
raise ValueError(msg) from exc
return_value = parse_exact_value(value)
else:
return_value = Value(Decimal(value))
Expand Down
89 changes: 19 additions & 70 deletions src/api/res.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from decimal import Decimal
from typing import Union, List, Tuple
from plum import dispatch, overload

from api.printable_result import PrintableResult
from api import parsers
Expand All @@ -16,18 +15,7 @@
import api.config as c # pylint: disable=wrong-import-position,ungrouped-imports


@overload
def res(
name: str,
value: Union[float, int, str, Decimal],
unit: str = "",
sigfigs: Union[int, None] = None,
decimal_places: Union[int, None] = None,
) -> PrintableResult:
return res(name, value, [], unit, sigfigs, decimal_places)


@overload
# pylint: disable-next=too-many-arguments, too-many-locals
def res(
name: str,
value: Union[float, int, str, Decimal],
Expand All @@ -39,56 +27,13 @@ def res(
List[Union[float, int, str, Decimal, Tuple[Union[float, int, str, Decimal], str]]],
None,
] = None,
sigfigs: Union[int, None] = None,
decimal_places: Union[int, None] = None,
) -> PrintableResult:
return res(name, value, uncert, "", sigfigs, decimal_places)


@overload
def res(
name: str,
value: Union[float, int, str, Decimal],
sigfigs: Union[int, None] = None,
decimal_places: Union[int, None] = None,
) -> PrintableResult:
return res(name, value, [], "", sigfigs, decimal_places)


@overload
# pylint: disable=too-many-arguments
def res(
name: str,
value: Union[float, int, str, Decimal],
sys: Union[float, Decimal],
stat: Union[float, Decimal],
unit: str = "",
sys: Union[float, int, str, Decimal, None] = None,
stat: Union[float, int, str, Decimal, None] = None,
sigfigs: Union[int, None] = None,
decimal_places: Union[int, None] = None,
) -> PrintableResult:
return res(name, value, [(sys, "sys"), (stat, "stat")], unit, sigfigs, decimal_places)


@overload
# pylint: disable=too-many-arguments
def res(
name: str,
value: Union[float, int, str, Decimal],
uncert: Union[
float,
str,
Decimal,
Tuple[Union[float, int, str, Decimal], str],
List[Union[float, int, str, Decimal, Tuple[Union[float, int, str, Decimal], str]]],
None,
] = None,
unit: str = "",
sigfigs: Union[int, None] = None,
decimal_places: Union[int, None] = None,
) -> PrintableResult:
if uncert is None:
uncert = []

):
# Verify user input
if sigfigs is not None and decimal_places is not None:
raise ValueError(error_messages.SIGFIGS_AND_DECIMAL_PLACES_AT_SAME_TIME)

Expand All @@ -98,6 +43,20 @@ def res(
if decimal_places is not None and isinstance(value, str):
raise ValueError(error_messages.DECIMAL_PLACES_AND_EXACT_VALUE_AT_SAME_TIME)

sys_or_stat_specified = sys is not None or stat is not None
if uncert is not None and sys_or_stat_specified:
raise ValueError(error_messages.UNCERT_AND_SYS_STAT_AT_SAME_TIME)

if sys_or_stat_specified:
uncert = []
if sys is not None:
uncert.append((sys, "sys"))
if stat is not None:
uncert.append((stat, "stat"))

if uncert is None:
uncert = []

# Parse user input
name_res = parsers.parse_name(name)
value_res = parsers.parse_value(value)
Expand All @@ -124,13 +83,3 @@ def res(
_export(immediate_export_path, print_completed=False)

return printable_result


# Hack for method "overloading" in Python
# see https://beartype.github.io/plum/integration.html
# This is a good writeup: https://stackoverflow.com/a/29091980/
@dispatch
def res(*args, **kwargs) -> object: # pylint: disable=unused-argument
# This method only scans for all `overload`-decorated methods
# and properly adds them as Plum methods.
pass
7 changes: 7 additions & 0 deletions src/application/error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
DECIMAL_PLACES_AND_EXACT_VALUE_AT_SAME_TIME = (
"You can't set decimal places and supply an exact value. Please do one or the other."
)
UNCERT_AND_SYS_STAT_AT_SAME_TIME = (
"You can't set uncertainties and systematic/statistical uncertainties at the same time. "
"Please provide either the `uncert` param or the `sys`/`stat` params."
)

# Parser error messages (generic)
STRING_MUST_BE_NUMBER = "String value must be a valid number, not {value}"
Expand All @@ -35,6 +39,9 @@
UNCERTAINTIES_MUST_BE_TUPLES_OR = (
"Each uncertainty must be a tuple or a float/int/Decimal/str, not {type}"
)
UNIT_NOT_PASSED_AS_KEYWORD_ARGUMENT = (
"Could it be the case you provided a unit but forgot `unit=` in front of it?"
)

# Helpers:
PRECISION_TOO_LOW = (
Expand Down
46 changes: 23 additions & 23 deletions tests/playground.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,35 @@
# wiz.res("", 42.0).print()
# -> Error: "name must not be empty"

wiz.res("a911", 1.05, r"\mm\s\per\N\kg")
wiz.res("a911", 1.05, unit=r"\mm\s\per\N\kg")
# wiz.res("a911", "1.052", 0.25, r"\mm\s\per\N\kg")

wiz.res("1 b", 1.0, 0.01, r"\per\mm\cubed")
wiz.res("1 b", 1.0, 0.01, unit=r"\per\mm\cubed")

# wiz.config(decimal_places=-1, sigfigs_fallback=3)

wiz.res("c big", 1.0, (0.01, "systematic"), r"\mm")
wiz.res("d", 1.0e10, [(0.01e10, "systematic"), (0.0294999e10, "stat")], r"\mm\per\second\squared")
wiz.res("e", "1.0", r"\mm")
wiz.res("d", 1.0e10, [(0.01e10, "sysyeah"), (0.0294999e10, "statyeah")], r"\mm\per\second^2")
# wiz.res("e", "1.0", r"\mm") # -> except error message that maybe we have forgotten to put `unit=`

wiz.res("f", "1.0e1", 25e-1)
wiz.res("g", 42)
wiz.res("h", 42, 13.0, 24.0)
wiz.res("h&", 42, 13.0, 24.0)
wiz.res("i", Decimal("42.0e-30"), Decimal("0.1e-31"), r"\m")
wiz.res("i", Decimal("42.0e-30"), Decimal("0.1e-31"), Decimal("0.05e-31"), r"\m\per\s\squared")
wiz.res("j", 0.009, None, None, 2)
# wiz.res("k", 1.55, 0.0, r"\tesla") # -> uncertainty must be positive

# wiz.res("k", 3, 1, r"\tesla") # -> plum: Could not be resolved
# TODO: Find out if one can adjust the plum.resolver.NotFoundLookupError such that
# we can give better hints, e.g. "you cannot pass in value and uncertainty as integers"

# wiz.res("g", 1.0, sys=0.01, stat=0.02, unit=r"\mm").print()
# g: (1.0 ± 0.01 sys ± 0.02 stat) \mm
# TODO: Why does this not work?
# -> This fix might help: https://github.com/beartype/plum/issues/40#issuecomment-1836613508

# The following wont' work as we can't have positional arguments (here: unit)
# after keyword arguments (here: uncert)
# wiz.res("d", 1.0, uncert=[(0.01, "systematic"), (0.02, "stat")], r"\mm").print()
wiz.res("h", 42, sys=13.0, stat=24.0)
wiz.res("h&", 42, sys=13.0, stat=24.0)

wiz.res("i", Decimal("42.0e-30"), Decimal("0.1e-31"), unit=r"\m")
wiz.res(
"i",
Decimal("42.0e-30"),
sys=Decimal("0.1e-31"),
stat=Decimal("0.05e-31"),
unit=r"\m\per\s\squared",
)
wiz.res("j", 0.009, None, "", 2) # really bad, but this is valid
# wiz.res("k", 1.55, 0.0, unit=r"\tesla") # -> uncertainty must be positive
wiz.res("k", 3, 1, r"\tesla") # integers work as well, yeah
wiz.res("l", 1.0, sys=0.01, stat=0.02, unit=r"\mm").print()
wiz.res("m", 1.0, uncert=[(0.01, "systematic"), (0.02, "stat")], unit=r"\mm").print()

# wiz.table(
# "name",
Expand All @@ -75,6 +73,8 @@
# horizontal = True,
# )

wiz.res("Tour Eiffel Height", "330.3141516", "0.5", r"\m")
wiz.res("g Another Test", 9.81, 0.78, unit=r"\m/\s^2")

#############################
# Export
Expand Down

0 comments on commit 3572e8f

Please sign in to comment.