Skip to content

Commit

Permalink
properly deprecate the current behavior of the default_format option (
Browse files Browse the repository at this point in the history
#1419)

* ignore magnitude formats for units

* convert non-truthy to empty string

* match longer flags first

i.e. match `cfunits` before `cf`. There might be a better way to do
this, though.

* add a test to check for the deprecation

* consistently split between mspec and uspec

which allows emitting a DeprecationWarning

* correct the explanation of where the default format is coming from

* use `reverse=True` instead of sorting with `-len(x)`

Co-authored-by: Jules Chéron <43635101+jules-ch@users.noreply.github.com>

* make sure the warning is raised correctly
  • Loading branch information
keewis committed Feb 23, 2022
1 parent 43287b3 commit ff15a14
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 22 deletions.
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:

This comment has been minimized.

Copy link
@CD3

CD3 May 4, 2022

Contributor

This should not applied if the x modifier is present.

if 'L' in uspec and 'x' not in uspec:
   mstr = ...
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 @@ -262,6 +262,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

0 comments on commit ff15a14

Please sign in to comment.