Skip to content

Commit 3d49103

Browse files
authored
Merge pull request #144 from dh-tech/feature/revise-repr
Revise __repr__ methods to return strings that can be used with eval()
2 parents 3da4e22 + 8b30880 commit 3d49103

File tree

8 files changed

+112
-37
lines changed

8 files changed

+112
-37
lines changed

README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,13 @@ earliest and latest possible dates for comparison purposes so you can
121121
sort dates and compare with equals, greater than, and less than. You
122122
can also compare with python `datetime.date` objects.
123123

124-
```python
124+
```python
125125
>>> november7_2020 = Undate(2020, 11, 7)
126126
>>> november_2001 = Undate(2001, 11)
127127
>>> year2k = Undate(2000)
128128
>>> ad100 = Undate(100)
129129
>>> sorted([november7_2020, november_2001, year2k, ad100])
130-
[<Undate 0100>, <Undate 2000>, <Undate 2001-11>, <Undate 2020-11-07>]
130+
[undate.Undate(year=100, calendar="Gregorian"), undate.Undate(year=2000, calendar="Gregorian"), undate.Undate(year=2001, month=11, calendar="Gregorian"), undate.Undate(year=2020, month=11, day=7, calendar="Gregorian")]
131131
>>> november7_2020 > november_2001
132132
True
133133
>>> year2k < ad100
@@ -161,17 +161,17 @@ and latest date as part of the range.
161161
```python
162162
>>> from undate import UndateInterval
163163
>>> UndateInterval(Undate(1900), Undate(2000))
164-
<UndateInterval 1900/2000>
164+
undate.UndateInterval(earliest=undate.Undate(year=1900, calendar="Gregorian"), latest=undate.Undate(year=2000, calendar="Gregorian"))
165165
>>> UndateInterval(Undate(1801), Undate(1900), label="19th century")
166+
undate.UndateInterval(earliest=undate.Undate(year=1801, calendar="Gregorian"), latest=undate.Undate(year=1900, calendar="Gregorian"), label="19th century")
166167
>>> UndateInterval(Undate(1801), Undate(1900), label="19th century").duration().days
167168
36524
168-
<UndateInterval '19th century' (1801/1900)>
169169
>>> UndateInterval(Undate(1901), Undate(2000), label="20th century")
170-
<UndateInterval '20th century' (1901/2000)>
170+
undate.UndateInterval(earliest=undate.Undate(year=1901, calendar="Gregorian"), latest=undate.Undate(year=2000, calendar="Gregorian"), label="20th century")
171171
>>> UndateInterval(latest=Undate(2000)) # before 2000
172-
<UndateInterval ../2000>
172+
undate.UndateInterval(latest=undate.Undate(year=2000, calendar="Gregorian"))
173173
>>> UndateInterval(Undate(1900)) # after 1900
174-
<UndateInterval 1900/>
174+
undate.UndateInterval(earliest=undate.Undate(year=1900, calendar="Gregorian"))
175175
>>> UndateInterval(Undate(1900), Undate(2000), label="19th century").duration().days
176176
36890
177177
>>> UndateInterval(Undate(2000, 1, 1), Undate(2000, 1,31)).duration().days
@@ -186,15 +186,15 @@ are "ISO8601" and "EDTF" and supported calendars.
186186
```python
187187
>>> from undate import Undate
188188
>>> Undate.parse("2002", "ISO8601")
189-
<Undate 2002>
189+
undate.Undate(year=2002, calendar="Gregorian")
190190
>>> Undate.parse("2002-05", "EDTF")
191-
<Undate 2002-05>
191+
undate.Undate(year=2002, month=5, calendar="Gregorian")
192192
>>> Undate.parse("--05-03", "ISO8601")
193-
<Undate --05-03>
193+
undate.Undate(month=5, day=3, calendar="Gregorian")
194194
>>> Undate.parse("--05-03", "ISO8601").format("EDTF")
195195
'XXXX-05-03'
196-
>>> Undate.parse("1800/1900")
197-
<UndateInterval 1800/1900>
196+
>>> Undate.parse("1800/1900", format="EDTF")
197+
undate.UndateInterval(earliest=undate.Undate(year=1800, calendar="Gregorian"), latest=undate.Undate(year=1900, calendar="Gregorian"))
198198
```
199199

200200
### Calendars
@@ -215,26 +215,26 @@ comparison across dates from different calendars.
215215
>>> from undate import Undate
216216
>>> tammuz4816 = Undate.parse("26 Tammuz 4816", "Hebrew")
217217
>>> tammuz4816
218-
<Undate '26 Tammuz 4816 Anno Mundi' 4816-04-26 (Hebrew)>
218+
undate.Undate(year=4816, month=4, day=26, label="26 Tammuz 4816 Anno Mundi", calendar="Hebrew")
219219
>>> rajab495 = Undate.parse("Rajab 495", "Islamic")
220220
>>> rajab495
221-
<Undate 'Rajab 495 Hijrī' 0495-07 (Islamic)>
221+
undate.Undate(year=495, month=7, label="Rajab 495 Islamic", calendar="Islamic")
222222
>>> y2k = Undate.parse("2001", "EDTF")
223223
>>> y2k
224-
<Undate 2001 (Gregorian)>
224+
undate.Undate(year=2001, calendar="Gregorian")
225225
>>> [str(d.earliest) for d in [rajab495, tammuz4816, y2k]]
226226
['1102-04-28', '1056-07-17', '2001-01-01']
227227
>>> [str(d.precision) for d in [rajab495, tammuz4816, y2k]]
228228
['MONTH', 'DAY', 'YEAR']
229229
>>> sorted([rajab495, tammuz4816, y2k])
230-
[<Undate '26 Tammuz 4816 Anno Mundi' 4816-04-26 (Hebrew)>, <Undate 'Rajab 495 Hijrī' 0495-07 (Islamic)>, <Undate 2001 (Gregorian)>]
230+
[undate.Undate(year=4816, month=4, day=26, label="26 Tammuz 4816 Anno Mundi", calendar="Hebrew"), undate.Undate(year=495, month=7, label="Rajab 495 Islamic", calendar="Islamic"), undate.Undate(year=2001, calendar="Gregorian")]
231231
```
232232

233233
---
234234

235-
For more examples, refer to the code notebooks included in the[examples]
236-
(https://github.com/dh-tech/undate-python/tree/main/examples/) in this
237-
repository.
235+
For more examples, refer to the code notebooks included in the
236+
[examples](https://github.com/dh-tech/undate-python/tree/main/examples/)
237+
directory in this repository.
238238

239239
## Documentation
240240

src/undate/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
__version__ = "0.6.0.dev0"
22

3-
from undate.date import DatePrecision
3+
from undate.date import DatePrecision, UnDelta
44
from undate.undate import Undate, Calendar
55
from undate.interval import UndateInterval
66

7-
__all__ = ["Undate", "UndateInterval", "Calendar", "DatePrecision", "__version__"]
7+
__all__ = [
8+
"Undate",
9+
"UndateInterval",
10+
"Calendar",
11+
"DatePrecision",
12+
"UnDelta",
13+
"__version__",
14+
]

src/undate/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def __init__(self, *days: int):
145145
def __repr__(self):
146146
# customize string representation for simpler notation; default
147147
# specifies full UnInt initialization with upper and lower keywords
148-
return f"{self.__class__.__name__}(days=[{self.days.lower},{self.days.upper}])"
148+
return f"undate.{self.__class__.__name__}({self.days.lower},{self.days.upper})"
149149

150150
def __eq__(self, other: object) -> bool:
151151
# is an uncertain duration ever *equal* another, even if the values are the same?

src/undate/interval.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,15 @@ def format(self, format) -> str:
7070
raise ValueError(f"Unsupported format '{format}'")
7171

7272
def __repr__(self) -> str:
73-
if self.label:
74-
return "<UndateInterval '%s' (%s)>" % (self.label, self)
75-
return "<UndateInterval %s>" % self
73+
init_opts = {
74+
"earliest": repr(self.earliest) if self.earliest else None,
75+
"latest": repr(self.latest) if self.latest else None,
76+
"label": f"{self.label!r}" if self.label else None,
77+
}
78+
init_str = ", ".join(
79+
[f"{key}={val}" for key, val in init_opts.items() if val is not None]
80+
)
81+
return f"undate.UndateInterval({init_str})"
7682

7783
def __eq__(self, other) -> bool:
7884
# currently doesn't support comparison with any other types

src/undate/undate.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,15 @@ def __str__(self) -> str:
250250
return self.converter.to_string(self)
251251

252252
def __repr__(self) -> str:
253-
label_str = f" '{self.label}'" if self.label else ""
254-
return f"<Undate{label_str} {self} ({self.calendar.name.title()})>"
253+
init_opts = {k: v for k, v in self.initial_values.items() if v is not None}
254+
# include label if set
255+
if self.label:
256+
init_opts["label"] = self.label
257+
# always include calendar
258+
init_opts["calendar"] = self.calendar.value.title()
259+
# combine parameters; use !r to quote strings
260+
init_str = ", ".join([f"{key}={val!r}" for key, val in init_opts.items()])
261+
return f"undate.Undate({init_str})"
255262

256263
@classmethod
257264
def parse(cls, date_string, format) -> Union["Undate", UndateInterval]:

tests/test_date.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,20 @@ def test_init_validation(self):
230230
UnDelta(10)
231231

232232
def test_repr(self):
233-
# customized string representation
234-
assert repr(UnDelta(28, 29)) == "UnDelta(days=[28,29])"
233+
# test customized string representation
234+
235+
# import undate to test eval of fully-qualified repr string
236+
import undate # noqa: F401
237+
238+
feb_undelt = UnDelta(28, 29)
239+
assert repr(feb_undelt) == "undate.UnDelta(28,29)"
240+
# can't compare directly because uncertain deltas aren't equal,
241+
# but compare values
242+
assert eval(repr(feb_undelt.days.lower)) == feb_undelt.days.lower
243+
assert eval(repr(feb_undelt.days.upper)) == feb_undelt.days.upper
244+
245+
larger_undelt = UnDelta(10, 12, 14, 16)
246+
assert repr(larger_undelt) == "undate.UnDelta(10,16)"
235247

236248
def test_eq(self):
237249
# uncertain deltas are not equivalent

tests/test_interval.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,33 @@ def test_format(self):
5656
assert open_end.format("ISO8601") == "2000/"
5757

5858
def test_repr(self):
59+
# import undate to test eval of fully-qualified repr string
60+
import undate # noqa: F401
61+
62+
# interval with start and end
63+
closed_interval = UndateInterval(Undate(2022), Undate(2023))
64+
assert (
65+
repr(closed_interval)
66+
== f"undate.UndateInterval(earliest={repr(closed_interval.earliest)}, latest={repr(closed_interval.latest)})"
67+
)
68+
# should be able to evaluate repr string to get an equivalent object
69+
assert eval(repr(closed_interval)) == closed_interval
70+
# interval with a label
71+
fancy_epoch = UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch")
5972
assert (
60-
repr(UndateInterval(Undate(2022), Undate(2023)))
61-
== "<UndateInterval 2022/2023>"
73+
repr(fancy_epoch)
74+
== f"undate.UndateInterval(earliest={repr(fancy_epoch.earliest)}, latest={repr(fancy_epoch.latest)}, label='Fancy Epoch')"
75+
)
76+
assert eval(repr(fancy_epoch)) == fancy_epoch
77+
78+
open_interval = UndateInterval(
79+
Undate(33),
6280
)
6381
assert (
64-
repr(UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch"))
65-
== "<UndateInterval 'Fancy Epoch' (2022/2023)>"
82+
repr(open_interval)
83+
== f"undate.UndateInterval(earliest={repr(open_interval.earliest)})"
6684
)
85+
assert eval(repr(open_interval)) == open_interval
6786

6887
def test_str_open_range(self):
6988
# 900 -

tests/test_undate.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,36 @@ def test_partially_known_str(self):
2929
# assert str(Undate(2022, day=7)) == "2022-XX-07" @ currently returns 2022-07
3030

3131
def test_repr(self):
32-
assert repr(Undate(2022, 11, 7)) == "<Undate 2022-11-07 (Gregorian)>"
32+
# import undate to test eval of fully-qualified undate repr string
33+
import undate # noqa: F401
34+
35+
nov2022 = Undate(2022, 11, 7)
36+
# repr string should provide sufficient details to initialize
37+
assert (
38+
repr(nov2022)
39+
== "undate.Undate(year=2022, month=11, day=7, calendar='Gregorian')"
40+
)
41+
# eval on repr string should be equivalent to the object
42+
assert eval(repr(nov2022)) == nov2022
43+
nov2022_labeled = Undate(2022, 11, 7, label="A Special Day")
44+
assert (
45+
repr(nov2022_labeled)
46+
== "undate.Undate(year=2022, month=11, day=7, label='A Special Day', calendar='Gregorian')"
47+
)
48+
assert eval(repr(nov2022_labeled)) == nov2022_labeled
49+
# different calendar, missing fields
50+
islamic_date = Undate(484, calendar=Calendar.ISLAMIC)
51+
assert repr(islamic_date) == "undate.Undate(year=484, calendar='Islamic')"
52+
assert eval(repr(islamic_date)) == islamic_date
53+
54+
# test string values for month/day
55+
unknown_year = Undate(month="1X", day="3X")
3356
assert (
34-
repr(Undate(2022, 11, 7, label="A Special Day"))
35-
== "<Undate 'A Special Day' 2022-11-07 (Gregorian)>"
57+
repr(unknown_year)
58+
== "undate.Undate(month='1X', day='3X', calendar='Gregorian')"
3659
)
37-
assert repr(Undate(484, calendar=Calendar.ISLAMIC)) == "<Undate 0484 (Islamic)>"
60+
# unknown dates aren't equal, but string representation should match
61+
assert str(eval(repr(unknown_year))) == str(unknown_year)
3862

3963
def test_init_str(self):
4064
assert Undate("2000").earliest.year == 2000

0 commit comments

Comments
 (0)