Skip to content

Commit 2482af7

Browse files
committed
Make functions private and deprecate public aliases in time module
Prior to this change, the `Unit` enum and the `abs_timedelta` and `date_and_delta` functions were listed as public functions in the documentation but were not available from the root package: >>> import humanize >>> humanize.Unit Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'humanize' has no attribute 'Unit' >>> humanize.abs_timedelta Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'humanize' has no attribute 'abs_timedelta' This change makes these members private. In order to preserve backwards compatibility after this change, we provide aliases for these members which emit deprecation warnings.
1 parent e926626 commit 2482af7

2 files changed

Lines changed: 111 additions & 37 deletions

File tree

src/humanize/time.py

Lines changed: 108 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import datetime as dt
99
import math
10-
from enum import Enum
10+
import warnings
11+
from enum import Enum, EnumMeta
1112
from functools import total_ordering
1213

1314
from .i18n import gettext as _
@@ -23,7 +24,7 @@
2324

2425

2526
@total_ordering
26-
class Unit(Enum):
27+
class _Unit(Enum):
2728
MICROSECONDS = 0
2829
MILLISECONDS = 1
2930
SECONDS = 2
@@ -39,11 +40,48 @@ def __lt__(self, other):
3940
return NotImplemented
4041

4142

43+
class _UnitMeta(EnumMeta):
44+
"""Metaclass for an enum that emits deprecation warnings when accessed."""
45+
46+
def __getattribute__(self, name):
47+
warnings.warn(
48+
"`Unit` has been deprecated. "
49+
"The enum is still available as the private member `_Unit`.",
50+
DeprecationWarning,
51+
)
52+
return EnumMeta.__getattribute__(_Unit, name)
53+
54+
def __getitem__(cls, name):
55+
warnings.warn(
56+
"`Unit` has been deprecated. "
57+
"The enum is still available as the private member `_Unit`.",
58+
DeprecationWarning,
59+
)
60+
return _Unit.__getitem__(name)
61+
62+
def __call__(
63+
cls, value, names=None, *, module=None, qualname=None, type=None, start=1
64+
):
65+
warnings.warn(
66+
"`Unit` has been deprecated. "
67+
"The enum is still available as the private member `_Unit`.",
68+
DeprecationWarning,
69+
)
70+
return _Unit.__call__(
71+
value, names, module=module, qualname=qualname, type=type, start=start
72+
)
73+
74+
75+
class Unit(Enum, metaclass=_UnitMeta):
76+
# Temporary alias for _Unit to allow backwards-compatible usage.
77+
pass
78+
79+
4280
def _now():
4381
return dt.datetime.now()
4482

4583

46-
def abs_timedelta(delta):
84+
def _abs_timedelta(delta):
4785
"""Return an "absolute" value for a timedelta, always representing a time distance.
4886
4987
Args:
@@ -58,7 +96,27 @@ def abs_timedelta(delta):
5896
return delta
5997

6098

61-
def date_and_delta(value, *, now=None):
99+
def abs_timedelta(delta):
100+
"""Return an "absolute" value for a timedelta, always representing a time distance.
101+
102+
Args:
103+
delta (datetime.timedelta): Input timedelta.
104+
105+
Returns:
106+
datetime.timedelta: Absolute timedelta.
107+
108+
WARNING: This function has been deprecated. It is still available as the private
109+
member `_abs_timedelta`.
110+
"""
111+
warnings.warn(
112+
"`abs_timedelta` has been deprecated. "
113+
"It is still available as the private member `_abs_timedelta`.",
114+
DeprecationWarning,
115+
)
116+
return _abs_timedelta(delta)
117+
118+
119+
def _date_and_delta(value, *, now=None):
62120
"""Turn a value into a date and a timedelta which represents how long ago it was.
63121
64122
If that's not possible, return `(None, value)`.
@@ -78,7 +136,23 @@ def date_and_delta(value, *, now=None):
78136
date = now - delta
79137
except (ValueError, TypeError):
80138
return None, value
81-
return date, abs_timedelta(delta)
139+
return date, _abs_timedelta(delta)
140+
141+
142+
def date_and_delta(delta):
143+
"""Turn a value into a date and a timedelta which represents how long ago it was.
144+
145+
If that's not possible, return `(None, value)`.
146+
147+
WARNING: This function has been deprecated. It is still available as the private
148+
member `_date_and_delta`.
149+
"""
150+
warnings.warn(
151+
"`date_and_delta` has been deprecated. "
152+
"It is still available as the private member `_date_and_delta`.",
153+
DeprecationWarning,
154+
)
155+
return _date_and_delta(delta)
82156

83157

84158
def naturaldelta(
@@ -114,12 +188,12 @@ def naturaldelta(
114188
115189
assert naturaldelta(later, when=now) == "30 minutes"
116190
"""
117-
tmp = Unit[minimum_unit.upper()]
118-
if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS):
191+
tmp = _Unit[minimum_unit.upper()]
192+
if tmp not in (_Unit.SECONDS, _Unit.MILLISECONDS, _Unit.MICROSECONDS):
119193
raise ValueError(f"Minimum unit '{minimum_unit}' not supported")
120194
minimum_unit = tmp
121195

122-
date, delta = date_and_delta(value, now=when)
196+
date, delta = _date_and_delta(value, now=when)
123197
if date is None:
124198
return value
125199

@@ -133,13 +207,13 @@ def naturaldelta(
133207

134208
if not years and days < 1:
135209
if seconds == 0:
136-
if minimum_unit == Unit.MICROSECONDS and delta.microseconds < 1000:
210+
if minimum_unit == _Unit.MICROSECONDS and delta.microseconds < 1000:
137211
return (
138212
ngettext("%d microsecond", "%d microseconds", delta.microseconds)
139213
% delta.microseconds
140214
)
141-
elif minimum_unit == Unit.MILLISECONDS or (
142-
minimum_unit == Unit.MICROSECONDS
215+
elif minimum_unit == _Unit.MILLISECONDS or (
216+
minimum_unit == _Unit.MICROSECONDS
143217
and 1000 <= delta.microseconds < 1_000_000
144218
):
145219
milliseconds = delta.microseconds / 1000
@@ -218,7 +292,7 @@ def naturaltime(
218292
str: A natural representation of the input in a resolution that makes sense.
219293
"""
220294
now = when or _now()
221-
date, delta = date_and_delta(value, now=now)
295+
date, delta = _date_and_delta(value, now=now)
222296
if date is None:
223297
return value
224298
# determine tense by value only if datetime/timedelta were passed
@@ -270,7 +344,7 @@ def naturaldate(value) -> str:
270344
except (OverflowError, ValueError):
271345
# Date arguments out of range
272346
return value
273-
delta = abs_timedelta(value - dt.date.today())
347+
delta = _abs_timedelta(value - dt.date.today())
274348
if delta.days >= 5 * 365 / 12:
275349
return naturalday(value, "%b %d %Y")
276350
return naturalday(value)
@@ -284,20 +358,20 @@ def _quotient_and_remainder(value, divisor, unit, minimum_unit, suppress):
284358
represent the remainder because it would require a unit smaller than the
285359
`minimum_unit`.
286360
287-
>>> from humanize.time import _quotient_and_remainder, Unit
288-
>>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.DAYS, [])
361+
>>> from humanize.time import _quotient_and_remainder, _Unit
362+
>>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.DAYS, [])
289363
(1.5, 0)
290364
291365
If unit is in `suppress`, the quotient will be zero and the remainder will be the
292366
initial value. The idea is that if we cannot use `unit`, we are forced to use a
293367
lower unit so we cannot do the division.
294368
295-
>>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [Unit.DAYS])
369+
>>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.HOURS, [_Unit.DAYS])
296370
(0, 36)
297371
298372
In other case return quotient and remainder as `divmod` would do it.
299373
300-
>>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [])
374+
>>> _quotient_and_remainder(36, 24, _Unit.DAYS, _Unit.HOURS, [])
301375
(1, 12)
302376
303377
"""
@@ -316,20 +390,20 @@ def _carry(value1, value2, ratio, unit, min_unit, suppress):
316390
(carry to right). The idea is that if we cannot represent `value1` we need to
317391
represent it in a lower unit.
318392
319-
>>> from humanize.time import _carry, Unit
320-
>>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [Unit.DAYS])
393+
>>> from humanize.time import _carry, _Unit
394+
>>> _carry(2, 6, 24, _Unit.DAYS, _Unit.SECONDS, [_Unit.DAYS])
321395
(0, 54)
322396
323397
If the unit is the minimum unit, `value2` is divided by `ratio` and added to
324398
`value1` (carry to left). We assume that `value2` has a lower unit so we need to
325399
carry it to `value1`.
326400
327-
>>> _carry(2, 6, 24, Unit.DAYS, Unit.DAYS, [])
401+
>>> _carry(2, 6, 24, _Unit.DAYS, _Unit.DAYS, [])
328402
(2.25, 0)
329403
330404
Otherwise, just return the same input:
331405
332-
>>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [])
406+
>>> _carry(2, 6, 24, _Unit.DAYS, _Unit.SECONDS, [])
333407
(2, 6)
334408
"""
335409
if unit == min_unit:
@@ -345,21 +419,21 @@ def _suitable_minimum_unit(min_unit, suppress):
345419
346420
If not suppressed, return the same unit:
347421
348-
>>> from humanize.time import _suitable_minimum_unit, Unit
349-
>>> _suitable_minimum_unit(Unit.HOURS, []).name
422+
>>> from humanize.time import _suitable_minimum_unit, _Unit
423+
>>> _suitable_minimum_unit(_Unit.HOURS, []).name
350424
'HOURS'
351425
352426
But if suppressed, find a unit greather than the original one that is not
353427
suppressed:
354428
355-
>>> _suitable_minimum_unit(Unit.HOURS, [Unit.HOURS]).name
429+
>>> _suitable_minimum_unit(_Unit.HOURS, [_Unit.HOURS]).name
356430
'DAYS'
357431
358-
>>> _suitable_minimum_unit(Unit.HOURS, [Unit.HOURS, Unit.DAYS]).name
432+
>>> _suitable_minimum_unit(_Unit.HOURS, [_Unit.HOURS, _Unit.DAYS]).name
359433
'MONTHS'
360434
"""
361435
if min_unit in suppress:
362-
for unit in Unit:
436+
for unit in _Unit:
363437
if unit > min_unit and unit not in suppress:
364438
return unit
365439

@@ -373,12 +447,12 @@ def _suitable_minimum_unit(min_unit, suppress):
373447
def _suppress_lower_units(min_unit, suppress):
374448
"""Extend suppressed units (if any) with all units lower than the minimum unit.
375449
376-
>>> from humanize.time import _suppress_lower_units, Unit
377-
>>> [x.name for x in sorted(_suppress_lower_units(Unit.SECONDS, [Unit.DAYS]))]
450+
>>> from humanize.time import _suppress_lower_units, _Unit
451+
>>> [x.name for x in sorted(_suppress_lower_units(_Unit.SECONDS, [_Unit.DAYS]))]
378452
['MICROSECONDS', 'MILLISECONDS', 'DAYS']
379453
"""
380454
suppress = set(suppress)
381-
for u in Unit:
455+
for u in _Unit:
382456
if u == min_unit:
383457
break
384458
suppress.add(u)
@@ -453,15 +527,15 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
453527
454528
```
455529
"""
456-
date, delta = date_and_delta(value)
530+
date, delta = _date_and_delta(value)
457531
if date is None:
458532
return value
459533

460-
suppress = [Unit[s.upper()] for s in suppress]
534+
suppress = [_Unit[s.upper()] for s in suppress]
461535

462536
# Find a suitable minimum unit (it can be greater the one that the
463537
# user gave us if it is suppressed).
464-
min_unit = Unit[minimum_unit.upper()]
538+
min_unit = _Unit[minimum_unit.upper()]
465539
min_unit = _suitable_minimum_unit(min_unit, suppress)
466540
del minimum_unit
467541

@@ -475,7 +549,7 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
475549
usecs = delta.microseconds
476550

477551
MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS, MONTHS, YEARS = list(
478-
Unit
552+
_Unit
479553
)
480554

481555
# Given DAYS compute YEARS and the remainder of DAYS as follows:
@@ -524,7 +598,7 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
524598
]
525599

526600
texts = []
527-
for unit, fmt in zip(reversed(Unit), fmts):
601+
for unit, fmt in zip(reversed(_Unit), fmts):
528602
singular_txt, plural_txt, value = fmt
529603
if value > 0 or (not texts and unit == min_unit):
530604
fmt_txt = ngettext(singular_txt, plural_txt, value)

tests/test_time.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ def test_date_and_delta():
6262
results = [(now - td(seconds=x), td(seconds=x)) for x in int_tests]
6363
for t in (int_tests, date_tests, td_tests):
6464
for arg, result in zip(t, results):
65-
date, d = time.date_and_delta(arg)
65+
date, d = time._date_and_delta(arg)
6666
assertEqualDatetime(date, result[0])
6767
assertEqualTimedelta(d, result[1])
68-
assert time.date_and_delta("NaN") == (None, "NaN")
68+
assert time._date_and_delta("NaN") == (None, "NaN")
6969

7070

7171
# Tests for the public interface of humanize.time
@@ -645,7 +645,7 @@ def test_precisedelta_bogus_call():
645645

646646

647647
def test_time_unit():
648-
years, minutes = time.Unit["YEARS"], time.Unit["MINUTES"]
648+
years, minutes = time._Unit["YEARS"], time._Unit["MINUTES"]
649649
assert minutes < years
650650
assert years > minutes
651651
assert minutes == minutes

0 commit comments

Comments
 (0)