Skip to content

Commit

Permalink
Merge pull request astropy#17586 from eerovaher/deprecate-unit-str-ar…
Browse files Browse the repository at this point in the history
…ithmetic

Deprecate multiplying and dividing units with `str` or `bytes`
  • Loading branch information
mhvk authored Dec 30, 2024
2 parents 888267d + 0318442 commit 116bf78
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 23 deletions.
28 changes: 21 additions & 7 deletions astropy/units/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from astropy.utils.compat import COPY_IF_NEEDED
from astropy.utils.decorators import deprecated, lazyproperty
from astropy.utils.exceptions import AstropyWarning
from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning
from astropy.utils.misc import isiterable

from .errors import UnitConversionError, UnitParserWarning, UnitsError, UnitsWarning
Expand Down Expand Up @@ -786,15 +786,26 @@ def __pow__(self, p: Real) -> CompositeUnit:
) from None
return CompositeUnit(1, [self], [sanitize_power(p)], _error_check=False)

def __truediv__(self, m):
if isinstance(m, (bytes, str)):
m = Unit(m)
@staticmethod
def _warn_about_operation_with_deprecated_type(op: str, other: bytes | str) -> None:
warnings.warn(
AstropyDeprecationWarning(
f"{op} involving a unit and a '{type(other).__name__}' instance are "
f"deprecated since v7.1. Convert {other!r} to a unit explicitly."
),
stacklevel=3,
)

def __truediv__(self, m):
if isinstance(m, UnitBase):
if m.is_unity():
return self
return CompositeUnit(1, [self, m], [1, -1], _error_check=False)

if isinstance(m, (bytes, str)):
self._warn_about_operation_with_deprecated_type("divisions", m)
return self / Unit(m)

try:
# Cannot handle this as Unit, re-try as Quantity
from .quantity import Quantity
Expand All @@ -805,6 +816,7 @@ def __truediv__(self, m):

def __rtruediv__(self, m):
if isinstance(m, (bytes, str)):
self._warn_about_operation_with_deprecated_type("divisions", m)
return Unit(m) / self

try:
Expand All @@ -825,16 +837,17 @@ def __rtruediv__(self, m):
return NotImplemented

def __mul__(self, m):
if isinstance(m, (bytes, str)):
m = Unit(m)

if isinstance(m, UnitBase):
if m.is_unity():
return self
elif self.is_unity():
return m
return CompositeUnit(1, [self, m], [1, 1], _error_check=False)

if isinstance(m, (bytes, str)):
self._warn_about_operation_with_deprecated_type("products", m)
return self * Unit(m)

# Cannot handle this as Unit, re-try as Quantity.
try:
from .quantity import Quantity
Expand All @@ -845,6 +858,7 @@ def __mul__(self, m):

def __rmul__(self, m):
if isinstance(m, (bytes, str)):
self._warn_about_operation_with_deprecated_type("products", m)
return Unit(m) * self

# Cannot handle this as Unit. Here, m cannot be a Quantity,
Expand Down
25 changes: 21 additions & 4 deletions astropy/units/tests/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from astropy.units.quantity import _UNIT_NOT_INITIALISED
from astropy.utils import isiterable
from astropy.utils.compat import COPY_IF_NEEDED
from astropy.utils.exceptions import AstropyWarning
from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning
from astropy.utils.masked import Masked

""" The Quantity class will represent a number + unit + uncertainty """
Expand Down Expand Up @@ -1608,16 +1608,33 @@ def test_quantity_initialized_with_quantity():


def test_quantity_string_unit():
q1 = 1.0 * u.m / "s"
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^divisions involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert 's' to a unit explicitly\.$"
),
):
q1 = 1.0 * u.m / "s"
assert q1.value == 1
assert q1.unit == (u.m / u.s)

q2 = q1 * "m"
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^products involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert 'm' to a unit explicitly\.$"
),
):
q2 = q1 * "m"
assert q2.unit == ((u.m * u.m) / u.s)


def test_quantity_invalid_unit_string():
with pytest.raises(ValueError):
with (
pytest.raises(ValueError),
pytest.warns(AstropyDeprecationWarning, match="^products involving .* a 'str'"),
):
"foo" * u.m


Expand Down
48 changes: 36 additions & 12 deletions astropy/units/tests/test_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,23 @@ def test_repr_latex():


def test_operations_with_strings():
assert u.m / "5s" == (u.m / (5.0 * u.s))
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^divisions involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert '5s' to a unit explicitly\.$"
),
):
assert u.m / "5s" == (u.m / (5.0 * u.s))

assert u.m * "5s" == (5.0 * u.m * u.s)
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^products involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert '5s' to a unit explicitly\.$"
),
):
assert u.m * "5s" == (5.0 * u.m * u.s)


def test_comparison():
Expand All @@ -756,19 +770,29 @@ def test_compose_into_arbitrary_units():


def test_unit_multiplication_with_string():
"""Check that multiplication with strings produces the correct unit."""
u1 = u.cm
us = "kg"
assert us * u1 == u.Unit(us) * u1
assert u1 * us == u1 * u.Unit(us)
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^products involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert 'kg' to a unit explicitly\.$"
),
):
assert "kg" * u.cm == u.kg * u.cm
with pytest.warns(AstropyDeprecationWarning, match="^products involving .* 'str'"):
assert u.cm * "kg" == u.cm * u.kg


def test_unit_division_by_string():
"""Check that multiplication with strings produces the correct unit."""
u1 = u.cm
us = "kg"
assert us / u1 == u.Unit(us) / u1
assert u1 / us == u1 / u.Unit(us)
with pytest.warns(
AstropyDeprecationWarning,
match=(
"^divisions involving a unit and a 'str' instance are deprecated since "
r"v7\.1\. Convert 'kg' to a unit explicitly\.$"
),
):
assert "kg" / u.cm == u.kg / u.cm
with pytest.warns(AstropyDeprecationWarning, match="^divisions involving .* 'str'"):
assert u.cm / "kg" == u.cm / u.kg


def test_sorted_bases():
Expand Down
2 changes: 2 additions & 0 deletions docs/changes/units/17586.api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Automatic conversion of a ``str`` or ``bytes`` instance to a unit when it is
multiplied or divided with an existing unit or quantity is deprecated.

0 comments on commit 116bf78

Please sign in to comment.