Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

properly deprecate the current behavior of the default_format option #1419

Merged
merged 15 commits into from
Feb 23, 2022
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
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Pint Changelog
0.19 (unreleased)
-----------------

- Deprecate the old format defaulting behavior and prepare for the new one (Issue #1407)
- Fix a bug for offset units of higher dimension, e.g. gauge pressure.
(Issue #1066, thanks dalito)
- Fix type hints of function wrapper (Issue #1431)
Expand Down
39 changes: 34 additions & 5 deletions docs/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,45 @@ specifications <formatspec>`. The basic format is:

where each part is optional and the order of these is arbitrary.

In case any part (except the modifier) is omitted, the corresponding value in
:py:attr:`Quantity.default_format` or :py:attr:`Unit.default_format` is filled in. If
that is not set (it evaluates to ``False``), :py:attr:`UnitRegistry.default_format` is
used. If both are not set, the global default of ``"D"`` and the magnitude's default
In case the format is omitted, the corresponding value in the object's
``.default_format`` attribute (:py:attr:`Quantity.default_format` or
:py:attr:`Unit.default_format`) is filled in. For example:

.. ipython::

In [1]: ureg = pint.UnitRegistry()
...: ureg.default_format = "~P"

In [2]: u = ureg.Unit("m ** 2 / s ** 2")
...: f"{u}"

In [3]: u.default_format = "~C"
...: f"{u}"

In [4]: u.default_format, ureg.default_format

In [5]: q = ureg.Quantity(1.25, "m ** 2 / s ** 2")
...: f"{q}"

In [6]: q.default_format = ".3fP"
...: f"{q}"

In [7]: q.default_format, ureg.default_format

.. note::

In the future, the magnitude and unit format spec will be evaluated
independently, such that with a global default of
``ureg.default_format = ".3f"`` and ``f"{q:P}`` the format that
will be used is ``".3fP"``.

If both are not set, the global default of ``"D"`` and the magnitude's default
format are used instead.

.. note::

Modifiers may be used without specifying any format: ``"~"`` is a valid format
specification.
specification and is equal to ``"~D"``.


Unit Format Specifications
Expand Down
10 changes: 8 additions & 2 deletions pint/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,14 +455,20 @@ def _tothe(power):
def extract_custom_flags(spec):
import re

flag_re = re.compile("(" + "|".join(list(_FORMATTERS.keys()) + ["~"]) + ")")
if not spec:
return ""

# sort by length, with longer items first
known_flags = sorted(_FORMATTERS.keys(), key=len, reverse=True)

flag_re = re.compile("(" + "|".join(known_flags + ["~"]) + ")")
custom_flags = flag_re.findall(spec)

return "".join(custom_flags)


def remove_custom_flags(spec):
for flag in list(_FORMATTERS.keys()) + ["~"]:
for flag in sorted(_FORMATTERS.keys(), key=len, reverse=True) + ["~"]:
if flag:
spec = spec.replace(flag, "")
return spec
Expand Down
56 changes: 42 additions & 14 deletions pint/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,18 +345,47 @@ def __format__(self, spec: str) -> str:
if self._REGISTRY.fmt_locale is not None:
return self.format_babel(spec)

spec = spec or self.default_format
mspec = remove_custom_flags(spec)
uspec = extract_custom_flags(spec)

default_mspec = remove_custom_flags(self.default_format)
default_uspec = extract_custom_flags(self.default_format)
if spec:
if not uspec and default_uspec:
warnings.warn(
(
"The given format spec does not contain a unit formatter."
" Falling back to the builtin defaults, but in the future"
" the unit formatter specified in the `default_format`"
" attribute will be used instead."
),
DeprecationWarning,
)
if not mspec and default_mspec:
warnings.warn(
(
"The given format spec does not contain a magnitude formatter."
" Falling back to the builtin defaults, but in the future"
" the magnitude formatter specified in the `default_format`"
" attribute will be used instead."
),
DeprecationWarning,
)
else:
mspec, uspec = default_mspec, default_uspec

# If Compact is selected, do it at the beginning
if "#" in spec:
spec = spec.replace("#", "")
# TODO: don't replace '#'
mspec = mspec.replace("#", "")
uspec = uspec.replace("#", "")
obj = self.to_compact()
else:
obj = self

if "L" in spec:
if "L" in uspec:
allf = plain_allf = r"{}\ {}"
elif "H" in spec:
elif "H" in uspec:
allf = plain_allf = "{} {}"
if iterable(obj.magnitude):
# Use HTML table instead of plain text template for array-likes
Expand All @@ -370,20 +399,19 @@ def __format__(self, spec: str) -> str:
else:
allf = plain_allf = "{} {}"

if "Lx" in spec:
if "Lx" in uspec:
# the LaTeX siunitx code
spec = spec.replace("Lx", "")
# TODO: add support for extracting options
opts = ""
ustr = siunitx_format_unit(obj.units._units, obj._REGISTRY)
allf = r"\SI[%s]{{{}}}{{{}}}" % opts
else:
# Hand off to unit formatting
uspec = extract_custom_flags(spec)
ustr = format(obj.units, uspec)
# TODO: only use `uspec` after completing the deprecation cycle
ustr = format(obj.units, mspec + uspec)

mspec = remove_custom_flags(spec)
if "H" in spec:
# mspec = remove_custom_flags(spec)
if "H" in uspec:
# HTML formatting
if hasattr(obj.magnitude, "_repr_html_"):
# If magnitude has an HTML repr, nest it within Pint's
Expand Down Expand Up @@ -417,7 +445,7 @@ def __format__(self, spec: str) -> str:
+ "</pre>"
)
elif isinstance(self.magnitude, ndarray):
if "L" in spec:
if "L" in uspec:
# Use ndarray LaTeX special formatting
mstr = ndarray_to_latex(obj.magnitude, mspec)
else:
Expand All @@ -432,12 +460,12 @@ def __format__(self, spec: str) -> str:
else:
mstr = format(obj.magnitude, mspec).replace("\n", "")

if "L" in spec:
if "L" in uspec:
mstr = self._exp_pattern.sub(r"\1\\times 10^{\2\3}", mstr)
elif "H" in spec or "P" in spec:
elif "H" in uspec or "P" in uspec:
m = self._exp_pattern.match(mstr)
_exp_formatter = (
_pretty_fmt_exponent if "P" in spec else lambda s: f"<sup>{s}</sup>"
_pretty_fmt_exponent if "P" in uspec else lambda s: f"<sup>{s}</sup>"
)
if m:
exp = int(m.group(2) + m.group(3))
Expand Down
18 changes: 18 additions & 0 deletions pint/testsuite/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,24 @@ def test_default_formatting(self, subtests):
ureg.default_format = spec
assert f"{x}" == result

def test_formatting_override_default_units(self):
ureg = UnitRegistry()
ureg.default_format = "~"
x = ureg.Quantity(4, "m ** 2")

assert f"{x:dP}" == "4 meter²"
with pytest.warns(DeprecationWarning):
assert f"{x:d}" == "4 meter ** 2"

def test_formatting_override_default_magnitude(self):
ureg = UnitRegistry()
ureg.default_format = ".2f"
x = ureg.Quantity(4, "m ** 2")

assert f"{x:dP}" == "4 meter²"
with pytest.warns(DeprecationWarning):
assert f"{x:D}" == "4 meter ** 2"

def test_exponent_formatting(self):
ureg = UnitRegistry()
x = ureg.Quantity(1e20, "meter")
Expand Down
2 changes: 1 addition & 1 deletion pint/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def __repr__(self) -> str:
return "<Unit('{}')>".format(self._units)

def __format__(self, spec) -> str:
spec = spec or extract_custom_flags(self.default_format)
spec = extract_custom_flags(spec or self.default_format)
if "~" in spec:
if not self._units:
return ""
Expand Down