Skip to content

Commit 5cd8641

Browse files
bsolomon1124mattlisiv
authored andcommitted
Accept additional type inputs for to & from_param (#45)
* Accept additional type inputs for to & from_param Accept date, datetime, float, int, str, or None for both date-related parameters. The input will be converted to either an ISO-8601 date string or datetime string using utils.stringify_date_param(). * Remove unused import
1 parent a6daa58 commit 5cd8641

4 files changed

Lines changed: 160 additions & 45 deletions

File tree

newsapi/newsapi_client.py

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import requests
22
from newsapi.newsapi_auth import NewsApiAuth
33
from newsapi import const
4+
from newsapi import utils
45
from newsapi.newsapi_exception import NewsAPIException
56
from sys import version_info
67

@@ -145,10 +146,12 @@ def get_everything(self, q=None, sources=None, domains=None, exclude_domains=Non
145146
146147
(str) exclude_domains - A comma_seperated string of domains to be excluded from the search
147148
148-
(str) from_param - A date and optional time for the oldest article allowed.
149-
(e.g. 2018-03-05 or 2018-03-05T03:46:15)
149+
(str, date, datetime, float, int, or None)
150+
from_param - A date and optional time for the oldest article allowed.
151+
(e.g. 2018-03-05 or 2018-03-05T03:46:15)
150152
151-
(str) to - A date and optional time for the newest article allowed.
153+
(str, date, datetime, float, int, or None)
154+
to - A date and optional time for the newest article allowed.
152155
153156
(str) language - The 2-letter ISO-639-1 code of the language you want to get headlines for. Valid values are:
154157
'ar','de','en','es','fr','he','it','nl','no','pt','ru','se','ud','zh'
@@ -192,31 +195,11 @@ def get_everything(self, q=None, sources=None, domains=None, exclude_domains=Non
192195

193196
# Search From This Date ...
194197
if from_param is not None:
195-
if is_valid_string(from_param):
196-
if (len(from_param)) >= 10:
197-
for i in range(len(from_param)):
198-
if (i == 4 and from_param[i] != '-') or (i == 7 and from_param[i] != '-'):
199-
raise ValueError('from_param should be in the format of YYYY-MM-DD')
200-
else:
201-
payload['from'] = from_param
202-
else:
203-
raise ValueError('from_param should be in the format of YYYY-MM-DD')
204-
else:
205-
raise TypeError('from_param should be of type str')
198+
payload['from'] = utils.stringify_date_param(from_param)
206199

207200
# ... To This Date
208201
if to is not None:
209-
if is_valid_string(to):
210-
if (len(to)) >= 10:
211-
for i in range(len(to)):
212-
if (i == 4 and to[i] != '-') or (i == 7 and to[i] != '-'):
213-
raise ValueError('to should be in the format of YYYY-MM-DD')
214-
else:
215-
payload['to'] = to
216-
else:
217-
raise ValueError('to param should be in the format of YYYY-MM-DD')
218-
else:
219-
raise TypeError('to param should be of type str')
202+
payload['to'] = utils.stringify_date_param(to)
220203

221204
# Language
222205
if language is not None:

newsapi/utils.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from __future__ import unicode_literals
2+
3+
__all__ = ("stringify_date_param",)
4+
5+
import datetime
6+
import re
7+
import sys
8+
9+
# Date in ISO-8601 format
10+
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
11+
DATE_LEN = len("YYYY-MM-DD")
12+
DATE_FMT = "%Y-%m-%d"
13+
14+
# Datetime in ISO-8601 format
15+
DATETIME_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
16+
DATETIME_LEN = len("YYYY-MM-DDTHH:MM:SS")
17+
DATETIME_FMT = "%Y-%m-%dT%H:%M:%S"
18+
19+
20+
def stringify_date_param(dt):
21+
if is_valid_string(dt):
22+
if len(dt) == DATE_LEN:
23+
validate_date_str(dt)
24+
elif len(dt) == DATETIME_LEN:
25+
validate_datetime_str(dt)
26+
else:
27+
raise ValueError(
28+
"Date input should be in format of either YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS"
29+
)
30+
return dt
31+
# Careful: datetime.datetime is subclass of datetime.date!
32+
elif isinstance(dt, datetime.datetime):
33+
# TODO: time zone
34+
return dt.strftime(DATETIME_FMT)
35+
elif isinstance(dt, datetime.date):
36+
return dt.strftime(DATE_FMT)
37+
elif is_valid_num(dt):
38+
return datetime.datetime.utcfromtimestamp(dt).strftime(DATETIME_FMT)
39+
else:
40+
raise TypeError(
41+
"Date input must be one of: str, date, datetime, float, int, or None"
42+
)
43+
44+
45+
def validate_date_str(datestr):
46+
if not DATE_RE.match(datestr):
47+
raise ValueError("Date input should be in format of YYYY-MM-DD")
48+
49+
50+
def validate_datetime_str(datetimestr):
51+
if not DATETIME_RE.match(datetimestr):
52+
raise ValueError("Datetime input should be in format of YYYY-MM-DDTHH:MM:SS")
53+
54+
55+
PY2 = sys.version_info[0] == 2
56+
PY3 = sys.version_info[0] == 3
57+
58+
if PY3:
59+
60+
def is_valid_string(var):
61+
return isinstance(var, str)
62+
63+
def is_valid_num(var):
64+
return isinstance(var, (int, float))
65+
66+
67+
elif PY2:
68+
69+
def is_valid_string(var):
70+
return isinstance(var, basestring)
71+
72+
def is_valid_num(var):
73+
return isinstance(var, (int, float, long))
74+
75+
76+
else:
77+
78+
def is_valid_string(var):
79+
raise SystemError(
80+
"unsupported version of python detected (supported versions: 2, 3)"
81+
)

tests/test_newsapi_client.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -102,26 +102,6 @@ def test_api_get_everything(self):
102102
with self.assertRaises(TypeError):
103103
self.api.get_everything(exclude_domains=exclude_domains)
104104

105-
# Raise TypeError is from_param param is not of type str
106-
from_param = 0
107-
with self.assertRaises(TypeError):
108-
self.api.get_everything(from_param=from_param)
109-
110-
# Raise ValueError if param is not in the format YYYY-MM-DD
111-
from_param = '2016-6-4'
112-
with self.assertRaises(ValueError):
113-
self.api.get_everything(from_param=from_param)
114-
115-
# Raise TypeError if to param is not of type str
116-
to = 1
117-
with self.assertRaises(TypeError):
118-
self.api.get_everything(to=to)
119-
120-
# Raise ValueError if to param is not in the format YYYY-MM-DD
121-
to = '2016-6-24'
122-
with self.assertRaises(ValueError):
123-
self.api.get_everything(to=to)
124-
125105
# Raise TypeError if language param is not of type str
126106
language = 0
127107
with self.assertRaises(TypeError):

tests/test_utils.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import datetime
2+
import unittest
3+
4+
from newsapi import utils
5+
6+
7+
class StringifyDateParamTest(unittest.TestCase):
8+
"""Test utils.stringify_date_param()."""
9+
10+
def test_str_input(self):
11+
for inp in (
12+
"2019-01-01",
13+
"2019-01-01T00:00:00",
14+
"2019-12-30T00:00:00",
15+
"2019-12-30T04:05:06",
16+
"2019-09-06T16:17:48",
17+
):
18+
if utils.PY3:
19+
with self.subTest(inp=inp):
20+
self.assertEqual(inp, utils.stringify_date_param(inp))
21+
else:
22+
self.assertEqual(inp, utils.stringify_date_param(inp))
23+
24+
def test_date_input(self):
25+
self.assertEqual(
26+
"2019-01-01", utils.stringify_date_param(datetime.date(2019, 1, 1))
27+
)
28+
self.assertEqual(
29+
"2019-12-30", utils.stringify_date_param(datetime.date(2019, 12, 30))
30+
)
31+
32+
def test_datetime_input(self):
33+
self.assertEqual(
34+
"2019-01-01T00:00:00",
35+
utils.stringify_date_param(datetime.datetime(2019, 1, 1)),
36+
)
37+
self.assertEqual(
38+
"2019-12-30T00:00:00",
39+
utils.stringify_date_param(datetime.datetime(2019, 12, 30)),
40+
)
41+
self.assertEqual(
42+
"2019-12-30T04:05:06",
43+
utils.stringify_date_param(datetime.datetime(2019, 12, 30, 4, 5, 6)),
44+
)
45+
46+
def test_unix_ts_input(self):
47+
self.assertEqual(
48+
"2019-09-06T16:17:48", utils.stringify_date_param(1567786668.826787)
49+
)
50+
self.assertEqual("2019-09-06T16:17:48", utils.stringify_date_param(1567786668))
51+
52+
def test_malformed_str_input(self):
53+
for bad_input in (
54+
"20190101",
55+
"01-01-2019",
56+
"2019-01-01 00:00:00",
57+
"2019-01-01T00:00:00-04:00",
58+
):
59+
if utils.PY3:
60+
with self.subTest(bad_input=bad_input):
61+
with self.assertRaises(ValueError):
62+
utils.stringify_date_param(bad_input)
63+
else:
64+
with self.assertRaises(ValueError):
65+
utils.stringify_date_param(bad_input)
66+
67+
def test_incorrect_type_input(self):
68+
with self.assertRaises(TypeError):
69+
utils.stringify_date_param(None)
70+
with self.assertRaises(TypeError):
71+
utils.stringify_date_param([datetime.date(2019, 12, 30)])

0 commit comments

Comments
 (0)