Skip to content

Commit

Permalink
Merge branch 'master' into nonreducing
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewgsavage committed Jun 27, 2024
2 parents 6c89e09 + 62046f0 commit 496df90
Show file tree
Hide file tree
Showing 17 changed files with 155 additions and 29 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
numpy: [null, "numpy>=1.23,<2.0.0", "numpy>=2.0.0rc1"]
uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"]
extras: [null]
include:
- python-version: "3.10" # Minimal versions
numpy: "numpy"
extras: matplotlib==2.2.5
numpy: "numpy>=1.23,<2.0.0"
extras: matplotlib==3.5.3
- python-version: "3.10"
numpy: "numpy"
uncertainties: "uncertainties"
extras: "sparse xarray netCDF4 dask[complete]==2023.4.0 graphviz babel==2.8 mip>=1.13"
extras: "sparse xarray netCDF4 dask[complete]==2024.5.1 graphviz babel==2.8 mip>=1.13"
- python-version: "3.10"
numpy: "numpy==1.26.1"
uncertainties: null
extras: "babel==2.15 matplotlib==3.9.0"
runs-on: ubuntu-latest

env:
Expand Down
13 changes: 12 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
Pint Changelog
==============

0.24 (unreleased)
0.24.1 (unreleased)
-----------------

- Fix custom formatter needing the registry object. (PR #2011)
- Support python 3.9 following difficulties installing with NumPy 2. (PR #2019)
- Fix default formatting of dimensionless unit issue. (PR #2012)
- Fix bug preventing custom formatters with modifiers working. (PR #2021)

0.24 (2024-06-07)
-----------------

- Fix detection of invalid conversion between offset and delta units. (PR #1905)
- Added dBW, decibel Watts, which is used in RF high power applications
- NumPy 2.0 support
(PR #1985, #1971)
- Implement numpy roll (Related to issue #981)
- Implement numpy correlate
(PR #1990)
- Add `dim_sort` function to _formatter_helpers.
- Add `dim_order` and `default_sort_func` properties to FullFormatter.
(PR #1926, fixes Issue #1841)
Expand All @@ -18,6 +28,7 @@ Pint Changelog
array resulting in incorrect units.
(PR #1677)
- Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal.
(PR #1977)
- Added refractive index units.
(PR #1816)
- Fix converting to offset units of higher dimension e.g. gauge pressure
Expand Down
1 change: 0 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
:alt: Latest Version

.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
:target: https://github.com/python/black
:target: https://github.com/astral-sh/ruff
:alt: Ruff

Expand Down
13 changes: 13 additions & 0 deletions docs/advanced/currencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,16 @@ currency on its own dimension, and then implement transformations::
More sophisticated formulas, e.g. dealing with flat fees and thresholds, can be
implemented with arbitrary python code by programmatically defining a context (see
:ref:`contexts`).

Currency Symbols
----------------

Many common currency symbols are not supported by the pint parser. A preprocessor can be used as a workaround:

.. doctest::

>>> import pint
>>> ureg = pint.UnitRegistry(preprocessors = [lambda s: s.replace("", "EUR")])
>>> ureg.define("euro = [currency] = € = EUR")
>>> print(ureg.Quantity("1 €"))
1 euro
1 change: 1 addition & 0 deletions docs/ecosystem.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Packages using pint:
- `thermo <https://github.com/CalebBell/thermo/>`_ Thermodynamic equilibrium calculations
- `Taurus <https://taurus-scada.org/>`_ Control system UI creation
- `InstrumentKit <https://github.com/instrumentkit/InstrumentKit>`_ Interacting with laboratory equipment over various buses.
- `NEMO <https://github.com/bje-/NEMO/>`_ Electricity production cost model
3 changes: 2 additions & 1 deletion docs/user/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ formats:
'2.3e-06 kilogram ** -1 * meter ** 3 * second ** -2'

where ``unit`` is a :py:class:`dict` subclass containing the unit names and
their exponents.
their exponents, ``registry`` is the current instance of :py:class:``UnitRegistry`` and
``options`` is not yet implemented.

You can choose to replace the complete formatter. Briefly, the formatter if an object with the
following methods: `format_magnitude`, `format_unit`, `format_quantity`, `format_uncertainty`,
Expand Down
6 changes: 3 additions & 3 deletions docs/user/log_units.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,16 @@ will not work:
.. doctest::

>>> -161.0 * ureg('dBm/Hz') == (-161.0 * ureg.dBm / ureg.Hz)
False
np.False_

But this will:

.. doctest::

>>> ureg('-161.0 dBm/Hz') == (-161.0 * ureg.dBm / ureg.Hz)
True
np.True_
>>> Q_(-161.0, 'dBm') / ureg.Hz == (-161.0 * ureg.dBm / ureg.Hz)
True
np.True_

To begin using this feature while avoiding problems, define logarithmic units
as single-unit quantities and convert them to their base units as quickly as
Expand Down
6 changes: 5 additions & 1 deletion pint/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
from typing import (
Any,
NoReturn,
TypeAlias, # noqa
)

if sys.version_info >= (3, 10):
from typing import TypeAlias # noqa
else:
from typing_extensions import TypeAlias # noqa

if sys.version_info >= (3, 11):
from typing import Self # noqa
else:
Expand Down
19 changes: 16 additions & 3 deletions pint/delegates/formatter/_compound_unit_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@
TYPE_CHECKING,
Any,
Literal,
TypeAlias,
TypedDict,
TypeVar,
)

from ...compat import babel_parse
from ...compat import TypeAlias, babel_parse
from ...util import NonReducingUnitsContainer, UnitsContainer

T = TypeVar("T")
Expand Down Expand Up @@ -83,11 +82,19 @@ def localize_per(
locale = babel_parse(locale)

patterns = locale._data["compound_unit_patterns"].get("per", None)
if patterns is None:
return default or "{}/{}"

patterns = patterns.get(length, None)
if patterns is None:
return default or "{}/{}"

return patterns.get(length, default or "{}/{}")
# babel 2.8
if isinstance(patterns, str):
return patterns

# babe; 2.15
return patterns.get("compound", default or "{}/{}")


@functools.lru_cache
Expand Down Expand Up @@ -259,6 +266,12 @@ def prepare_compount_unit(

# out: unit_name, unit_exponent

if len(out) == 0:
if "~" in spec:
return ([], [])
else:
return ([("dimensionless", 1)], [])

if "~" in spec:
if registry is None:
raise ValueError(
Expand Down
2 changes: 2 additions & 0 deletions pint/delegates/formatter/_format_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def join_mu(joint_fstring: str, mstr: str, ustr: str) -> str:
This avoids that `3 and `1 / m` becomes `3 1 / m`
"""
if ustr == "":
return mstr
if ustr.startswith("1 / "):
return joint_fstring.format(mstr, ustr[2:])
return joint_fstring.format(mstr, ustr)
Expand Down
2 changes: 2 additions & 0 deletions pint/delegates/formatter/_to_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def wrapper(func: Callable[[PlainUnit, UnitRegistry], str]):
raise ValueError(f"format {name!r} already exists") # or warn instead

class NewFormatter(BaseFormatter):
spec = name

def format_magnitude(
self,
magnitude: Magnitude,
Expand Down
18 changes: 13 additions & 5 deletions pint/delegates/formatter/full.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,20 @@ def get_formatter(self, spec: str):
if k in spec:
return v

try:
return REGISTERED_FORMATTERS[spec]
except KeyError:
pass
for k, v in REGISTERED_FORMATTERS.items():
if k in spec:
orphan_fmt = REGISTERED_FORMATTERS[k]
break
else:
return self._formatters["D"]

return self._formatters["D"]
try:
fmt = orphan_fmt.__class__(self._registry)
spec = getattr(fmt, "spec", spec)
self._formatters[spec] = fmt
return fmt
except Exception:
return orphan_fmt

def format_magnitude(
self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds]
Expand Down
23 changes: 20 additions & 3 deletions pint/testsuite/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,26 @@ def requires_numpy_at_least(version):
)


requires_babel = pytest.mark.skipif(
not HAS_BABEL, reason="Requires Babel with units support"
)
def requires_babel(tested_locales=[]):
if not HAS_BABEL:
return pytest.mark.skip("Requires Babel with units support")

import locale

default_locale = locale.getlocale(locale.LC_NUMERIC)
locales_unavailable = False
try:
for loc in tested_locales:
locale.setlocale(locale.LC_NUMERIC, loc)
except locale.Error:
locales_unavailable = True
locale.setlocale(locale.LC_NUMERIC, default_locale)

return pytest.mark.skipif(
locales_unavailable, reason="Tested locales not available."
)


requires_not_babel = pytest.mark.skipif(
HAS_BABEL, reason="Requires Babel not to be installed"
)
Expand Down
8 changes: 4 additions & 4 deletions pint/testsuite/test_babel.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_no_babel(func_registry):
distance.format_babel(locale="fr_FR", length="long")


@helpers.requires_babel()
@helpers.requires_babel(["fr_FR", "ro_RO"])
def test_format(func_registry):
ureg = func_registry
dirname = os.path.dirname(__file__)
Expand All @@ -36,7 +36,7 @@ def test_format(func_registry):
assert mks.format_babel(locale="fr_FR") == "métrique"


@helpers.requires_babel()
@helpers.requires_babel(["fr_FR", "ro_RO"])
def test_registry_locale():
ureg = UnitRegistry(fmt_locale="fr_FR")
dirname = os.path.dirname(__file__)
Expand All @@ -60,7 +60,7 @@ def test_registry_locale():
assert mks.format_babel(locale="fr_FR") == "métrique"


@helpers.requires_babel()
@helpers.requires_babel(["fr_FR"])
def test_unit_format_babel():
ureg = UnitRegistry(fmt_locale="fr_FR")
volume = ureg.Unit("ml")
Expand All @@ -85,7 +85,7 @@ def test_no_registry_locale(func_registry):
distance.format_babel()


@helpers.requires_babel()
@helpers.requires_babel(["fr_FR"])
def test_str(func_registry):
ureg = func_registry
d = 24.1 * ureg.meter
Expand Down
2 changes: 2 additions & 0 deletions pint/testsuite/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def test_split_format(format, default, flag, expected):
def test_register_unit_format(func_registry):
@fmt.register_unit_format("custom")
def format_custom(unit, registry, **options):
# Ensure the registry is correct..
registry.Unit(unit)
return "<formatted unit>"

quantity = 1.0 * func_registry.meter
Expand Down
52 changes: 50 additions & 2 deletions pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

import pytest

from pint import Context, DimensionalityError, UnitRegistry, get_application_registry
from pint import (
Context,
DimensionalityError,
UnitRegistry,
get_application_registry,
)
from pint.compat import np
from pint.delegates.formatter._compound_unit_helpers import sort_by_dimensionality
from pint.facets.plain.unit import UnitsContainer
Expand Down Expand Up @@ -908,7 +913,7 @@ def test_issue1674(self, module_registry):
arr_of_q * q_arr, np.array([Q_(2, "m^2"), Q_(8, "m s")], dtype="object")
)

@helpers.requires_babel()
@helpers.requires_babel(["es_ES"])
def test_issue_1400(self, sess_registry):
q1 = 3.1 * sess_registry.W
q2 = 3.1 * sess_registry.W / sess_registry.cm
Expand Down Expand Up @@ -1255,3 +1260,46 @@ def test_issue1949(registry_empty):
def test_issue1772(given, expected):
ureg = UnitRegistry(non_int_type=decimal.Decimal)
assert f"{ureg(given):Lx}" == expected


def test_issue2017():
ureg = UnitRegistry()

from pint import formatting as fmt

@fmt.register_unit_format("test")
def _test_format(unit, registry, **options):
print("format called")
proc = {u.replace("µ", "u"): e for u, e in unit.items()}
return fmt.formatter(
proc.items(),
as_ratio=True,
single_denominator=False,
product_fmt="*",
division_fmt="/",
power_fmt="{}{}",
parentheses_fmt="({})",
**options,
)

base_unit = ureg.microsecond
assert f"{base_unit:~test}" == "us"
assert f"{base_unit:test}" == "microsecond"


def test_issue2007():
ureg = UnitRegistry()
q = ureg.Quantity(1, "")
assert f"{q:P}" == "1 dimensionless"
assert f"{q:C}" == "1 dimensionless"
assert f"{q:D}" == "1 dimensionless"
assert f"{q:H}" == "1 dimensionless"

assert f"{q:L}" == "1\\ \\mathrm{dimensionless}"
# L returned '1\\ dimensionless' in pint 0.23

assert f"{q:Lx}" == "\\SI[]{1}{}"
assert f"{q:~P}" == "1"
assert f"{q:~C}" == "1"
assert f"{q:~D}" == "1"
assert f"{q:~H}" == "1"
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ classifiers = [
"Programming Language :: Python",
"Topic :: Scientific/Engineering",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
requires-python = ">=3.10"
requires-python = ">=3.9"
dynamic = ["version", "dependencies"]

[tool.setuptools.package-data]
Expand Down

0 comments on commit 496df90

Please sign in to comment.