Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 281d206

Browse files
committedJan 16, 2023
Move parse methods from covidcast
1 parent 7774f96 commit 281d206

31 files changed

+374
-349
lines changed
 

‎src/server/_params.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from ._exceptions import ValidationFailedException
1010
from .utils import days_in_range, weeks_in_range, guess_time_value_is_day, guess_time_value_is_week, IntRange, TimeValues, days_to_ranges, weeks_to_ranges
11+
from ._validate import require_any, require_all
1112

1213

1314
def _parse_common_multi_arg(key: str) -> List[Tuple[str, Union[bool, Sequence[str]]]]:
@@ -308,3 +309,177 @@ def parse_day_or_week_range_arg(key: str) -> TimeSet:
308309
if is_week:
309310
return TimeSet("week", [parse_week_range_arg(key)])
310311
return TimeSet("day", [parse_day_range_arg(key)])
312+
313+
314+
def _extract_value(key: Union[str, Sequence[str]]) -> Optional[str]:
315+
if isinstance(key, str):
316+
return request.values.get(key)
317+
for k in key:
318+
if k in request.values:
319+
return request.values[k]
320+
return None
321+
322+
323+
def _extract_list_value(key: Union[str, Sequence[str]]) -> List[str]:
324+
if isinstance(key, str):
325+
return request.values.getlist(key)
326+
for k in key:
327+
if k in request.values:
328+
return request.values.getlist(k)
329+
return []
330+
331+
332+
def extract_strings(key: Union[str, Sequence[str]]) -> Optional[List[str]]:
333+
s = _extract_list_value(key)
334+
if not s:
335+
# nothing to do
336+
return None
337+
# we can have multiple values
338+
return [v for vs in s for v in vs.split(",")]
339+
340+
341+
def extract_integer(key: Union[str, Sequence[str]]) -> Optional[int]:
342+
s = _extract_value(key)
343+
if not s:
344+
# nothing to do
345+
return None
346+
try:
347+
return int(s)
348+
except ValueError:
349+
raise ValidationFailedException(f"{key}: not a number: {s}")
350+
351+
352+
def extract_integers(key: Union[str, Sequence[str]]) -> Optional[List[IntRange]]:
353+
parts = extract_strings(key)
354+
if not parts:
355+
# nothing to do
356+
return None
357+
358+
def _parse_range(part: str):
359+
if "-" not in part:
360+
return int(part)
361+
r = part.split("-", 2)
362+
first = int(r[0])
363+
last = int(r[1])
364+
if first == last:
365+
# the first and last numbers are the same, just treat it as a singe value
366+
return first
367+
elif last > first:
368+
# add the range as an array
369+
return (first, last)
370+
# the range is inverted, this is an error
371+
raise ValidationFailedException(f"{key}: the given range is inverted")
372+
373+
try:
374+
values = [_parse_range(part) for part in parts]
375+
# check for invalid values
376+
return None if any(v is None for v in values) else values
377+
except ValueError as e:
378+
raise ValidationFailedException(f"{key}: not a number: {str(e)}")
379+
380+
381+
def parse_date(s: str) -> int:
382+
# parses a given string in format YYYYMMDD or YYYY-MM-DD to a number in the form YYYYMMDD
383+
try:
384+
return int(s.replace("-", ""))
385+
except ValueError:
386+
raise ValidationFailedException(f"not a valid date: {s}")
387+
388+
389+
def extract_date(key: Union[str, Sequence[str]]) -> Optional[int]:
390+
s = _extract_value(key)
391+
if not s:
392+
return None
393+
return parse_date(s)
394+
395+
396+
def extract_dates(key: Union[str, Sequence[str]]) -> Optional[TimeValues]:
397+
parts = extract_strings(key)
398+
if not parts:
399+
return None
400+
values: TimeValues = []
401+
402+
def push_range(first: str, last: str):
403+
first_d = parse_date(first)
404+
last_d = parse_date(last)
405+
if first_d == last_d:
406+
# the first and last numbers are the same, just treat it as a singe value
407+
return first_d
408+
if last_d > first_d:
409+
# add the range as an array
410+
return (first_d, last_d)
411+
# the range is inverted, this is an error
412+
raise ValidationFailedException(f"{key}: the given range is inverted")
413+
414+
for part in parts:
415+
if "-" not in part and ":" not in part:
416+
# YYYYMMDD
417+
values.append(parse_date(part))
418+
continue
419+
if ":" in part:
420+
# YYYY-MM-DD:YYYY-MM-DD
421+
range_part = part.split(":", 2)
422+
r = push_range(range_part[0], range_part[1])
423+
if r is None:
424+
return None
425+
values.append(r)
426+
continue
427+
# YYYY-MM-DD or YYYYMMDD-YYYYMMDD
428+
# split on the dash
429+
range_part = part.split("-")
430+
if len(range_part) == 2:
431+
# YYYYMMDD-YYYYMMDD
432+
r = push_range(range_part[0], range_part[1])
433+
if r is None:
434+
return None
435+
values.append(r)
436+
continue
437+
# YYYY-MM-DD
438+
values.append(parse_date(part))
439+
# success, return the list
440+
return values
441+
442+
def parse_source_signal_sets() -> List[SourceSignalSet]:
443+
ds = request.values.get("data_source")
444+
if ds:
445+
# old version
446+
require_any("signal", "signals", empty=True)
447+
signals = extract_strings(("signals", "signal"))
448+
if len(signals) == 1 and signals[0] == "*":
449+
return [SourceSignalSet(ds, True)]
450+
return [SourceSignalSet(ds, signals)]
451+
452+
if ":" not in request.values.get("signal", ""):
453+
raise ValidationFailedException("missing parameter: signal or (data_source and signal[s])")
454+
455+
return parse_source_signal_arg()
456+
457+
458+
def parse_geo_sets() -> List[GeoSet]:
459+
geo_type = request.values.get("geo_type")
460+
if geo_type:
461+
# old version
462+
require_any("geo_value", "geo_values", empty=True)
463+
geo_values = extract_strings(("geo_values", "geo_value"))
464+
if len(geo_values) == 1 and geo_values[0] == "*":
465+
return [GeoSet(geo_type, True)]
466+
return [GeoSet(geo_type, geo_values)]
467+
468+
if ":" not in request.values.get("geo", ""):
469+
raise ValidationFailedException("missing parameter: geo or (geo_type and geo_value[s])")
470+
471+
return parse_geo_arg()
472+
473+
474+
def parse_time_set() -> TimeSet:
475+
time_type = request.values.get("time_type")
476+
if time_type:
477+
# old version
478+
require_all("time_type", "time_values")
479+
time_values = extract_dates("time_values")
480+
return TimeSet(time_type, time_values)
481+
482+
if ":" not in request.values.get("time", ""):
483+
raise ValidationFailedException("missing parameter: time or (time_type and time_values)")
484+
485+
return parse_time_arg()

‎src/server/_query.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
from ._common import db
1919
from ._printer import create_printer, APrinter
2020
from ._exceptions import DatabaseErrorException
21-
from ._validate import extract_strings
22-
from ._params import GeoSet, SourceSignalSet, TimeSet
21+
from ._params import extract_strings, GeoSet, SourceSignalSet, TimeSet
2322
from .utils import time_values_to_ranges, IntRange, TimeValues
2423

2524

‎src/server/_validate.py

Lines changed: 0 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -55,132 +55,3 @@ def require_any(*values: str, empty=False) -> bool:
5555
if request.values.get(value) or (empty and value in request.values):
5656
return True
5757
raise ValidationFailedException(f"missing parameter: need one of [{', '.join(values)}]")
58-
59-
60-
def _extract_value(key: Union[str, Sequence[str]]) -> Optional[str]:
61-
if isinstance(key, str):
62-
return request.values.get(key)
63-
for k in key:
64-
if k in request.values:
65-
return request.values[k]
66-
return None
67-
68-
69-
def _extract_list_value(key: Union[str, Sequence[str]]) -> List[str]:
70-
if isinstance(key, str):
71-
return request.values.getlist(key)
72-
for k in key:
73-
if k in request.values:
74-
return request.values.getlist(k)
75-
return []
76-
77-
78-
def extract_strings(key: Union[str, Sequence[str]]) -> Optional[List[str]]:
79-
s = _extract_list_value(key)
80-
if not s:
81-
# nothing to do
82-
return None
83-
# we can have multiple values
84-
return [v for vs in s for v in vs.split(",")]
85-
86-
87-
def extract_integer(key: Union[str, Sequence[str]]) -> Optional[int]:
88-
s = _extract_value(key)
89-
if not s:
90-
# nothing to do
91-
return None
92-
try:
93-
return int(s)
94-
except ValueError:
95-
raise ValidationFailedException(f"{key}: not a number: {s}")
96-
97-
98-
def extract_integers(key: Union[str, Sequence[str]]) -> Optional[List[IntRange]]:
99-
parts = extract_strings(key)
100-
if not parts:
101-
# nothing to do
102-
return None
103-
104-
def _parse_range(part: str):
105-
if "-" not in part:
106-
return int(part)
107-
r = part.split("-", 2)
108-
first = int(r[0])
109-
last = int(r[1])
110-
if first == last:
111-
# the first and last numbers are the same, just treat it as a singe value
112-
return first
113-
elif last > first:
114-
# add the range as an array
115-
return (first, last)
116-
# the range is inverted, this is an error
117-
raise ValidationFailedException(f"{key}: the given range is inverted")
118-
119-
try:
120-
values = [_parse_range(part) for part in parts]
121-
# check for invalid values
122-
return None if any(v is None for v in values) else values
123-
except ValueError as e:
124-
raise ValidationFailedException(f"{key}: not a number: {str(e)}")
125-
126-
127-
def parse_date(s: str) -> int:
128-
# parses a given string in format YYYYMMDD or YYYY-MM-DD to a number in the form YYYYMMDD
129-
try:
130-
return int(s.replace("-", ""))
131-
except ValueError:
132-
raise ValidationFailedException(f"not a valid date: {s}")
133-
134-
135-
def extract_date(key: Union[str, Sequence[str]]) -> Optional[int]:
136-
s = _extract_value(key)
137-
if not s:
138-
return None
139-
return parse_date(s)
140-
141-
142-
def extract_dates(key: Union[str, Sequence[str]]) -> Optional[TimeValues]:
143-
parts = extract_strings(key)
144-
if not parts:
145-
return None
146-
values: TimeValues = []
147-
148-
def push_range(first: str, last: str):
149-
first_d = parse_date(first)
150-
last_d = parse_date(last)
151-
if first_d == last_d:
152-
# the first and last numbers are the same, just treat it as a singe value
153-
return first_d
154-
if last_d > first_d:
155-
# add the range as an array
156-
return (first_d, last_d)
157-
# the range is inverted, this is an error
158-
raise ValidationFailedException(f"{key}: the given range is inverted")
159-
160-
for part in parts:
161-
if "-" not in part and ":" not in part:
162-
# YYYYMMDD
163-
values.append(parse_date(part))
164-
continue
165-
if ":" in part:
166-
# YYYY-MM-DD:YYYY-MM-DD
167-
range_part = part.split(":", 2)
168-
r = push_range(range_part[0], range_part[1])
169-
if r is None:
170-
return None
171-
values.append(r)
172-
continue
173-
# YYYY-MM-DD or YYYYMMDD-YYYYMMDD
174-
# split on the dash
175-
range_part = part.split("-")
176-
if len(range_part) == 2:
177-
# YYYYMMDD-YYYYMMDD
178-
r = push_range(range_part[0], range_part[1])
179-
if r is None:
180-
return None
181-
values.append(r)
182-
continue
183-
# YYYY-MM-DD
184-
values.append(parse_date(part))
185-
# success, return the list
186-
return values

‎src/server/endpoints/afhsb.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from flask import Blueprint
44

55
from .._config import AUTH
6+
from .._params import extract_integers, extract_strings
67
from .._query import execute_queries, filter_integers, filter_strings
7-
from .._validate import check_auth_token, extract_integers, extract_strings, require_all
8+
from .._validate import check_auth_token, require_all
89

910
# first argument is the endpoint name
1011
bp = Blueprint("afhsb", __name__)

‎src/server/endpoints/cdc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask import Blueprint
22

33
from .._config import AUTH, NATION_REGION, REGION_TO_STATE
4-
from .._validate import require_all, extract_strings, extract_integers, check_auth_token
4+
from .._params import extract_strings, extract_integers
55
from .._query import filter_strings, execute_queries, filter_integers
6+
from .._validate import require_all, check_auth_token
67

78
# first argument is the endpoint name
89
bp = Blueprint("cdc", __name__)

‎src/server/endpoints/covid_hosp_facility.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("covid_hosp_facility", __name__)

‎src/server/endpoints/covid_hosp_facility_lookup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_strings, require_any
5+
from .._validate import require_any
56

67
# first argument is the endpoint name
78
bp = Blueprint("covid_hosp_facility_lookup", __name__)

‎src/server/endpoints/covid_hosp_state_timeseries.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings, extract_date
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integers, extract_strings, extract_date, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("covid_hosp_state_timeseries", __name__)

‎src/server/endpoints/covidcast.py

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
GeoSet,
1515
SourceSignalSet,
1616
TimeSet,
17+
extract_date,
18+
extract_dates,
19+
extract_integer,
1720
parse_geo_arg,
1821
parse_source_signal_arg,
1922
parse_time_arg,
@@ -22,17 +25,13 @@
2225
parse_single_source_signal_arg,
2326
parse_single_time_arg,
2427
parse_single_geo_arg,
28+
parse_geo_sets,
29+
parse_source_signal_sets,
30+
parse_time_sets,
2531
)
2632
from .._query import QueryBuilder, execute_query, run_query, parse_row, filter_fields
2733
from .._printer import create_printer, CSVPrinter
28-
from .._validate import (
29-
extract_date,
30-
extract_dates,
31-
extract_integer,
32-
extract_strings,
33-
require_all,
34-
require_any,
35-
)
34+
from .._validate import require_all
3635
from .._pandas import as_pandas, print_pandas
3736
from .covidcast_utils import compute_trend, compute_trends, compute_correlations, compute_trend_value, CovidcastMetaEntry
3837
from ..utils import shift_day_value, day_to_time_value, time_value_to_iso, time_value_to_day, shift_week_value, time_value_to_week, guess_time_value_is_day, week_to_time_value, TimeValues
@@ -45,52 +44,6 @@
4544
latest_table = "epimetric_latest_v"
4645
history_table = "epimetric_full_v"
4746

48-
def parse_source_signal_sets() -> List[SourceSignalSet]:
49-
ds = request.values.get("data_source")
50-
if ds:
51-
# old version
52-
require_any("signal", "signals", empty=True)
53-
signals = extract_strings(("signals", "signal"))
54-
if len(signals) == 1 and signals[0] == "*":
55-
return [SourceSignalSet(ds, True)]
56-
return [SourceSignalSet(ds, signals)]
57-
58-
if ":" not in request.values.get("signal", ""):
59-
raise ValidationFailedException("missing parameter: signal or (data_source and signal[s])")
60-
61-
return parse_source_signal_arg()
62-
63-
64-
def parse_geo_sets() -> List[GeoSet]:
65-
geo_type = request.values.get("geo_type")
66-
if geo_type:
67-
# old version
68-
require_any("geo_value", "geo_values", empty=True)
69-
geo_values = extract_strings(("geo_values", "geo_value"))
70-
if len(geo_values) == 1 and geo_values[0] == "*":
71-
return [GeoSet(geo_type, True)]
72-
return [GeoSet(geo_type, geo_values)]
73-
74-
if ":" not in request.values.get("geo", ""):
75-
raise ValidationFailedException("missing parameter: geo or (geo_type and geo_value[s])")
76-
77-
return parse_geo_arg()
78-
79-
80-
def parse_time_set() -> TimeSet:
81-
time_type = request.values.get("time_type")
82-
if time_type:
83-
# old version
84-
require_all("time_type", "time_values")
85-
time_values = extract_dates("time_values")
86-
return TimeSet(time_type, time_values)
87-
88-
if ":" not in request.values.get("time", ""):
89-
raise ValidationFailedException("missing parameter: time or (time_type and time_values)")
90-
91-
return parse_time_arg()
92-
93-
9447
@bp.route("/", methods=("GET", "POST"))
9548
def handle():
9649
source_signal_sets = parse_source_signal_sets()

‎src/server/endpoints/covidcast_meta.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from sqlalchemy import text
77

88
from .._common import db
9+
from .._params import extract_strings
910
from .._printer import create_printer
1011
from .._query import filter_fields
11-
from .._validate import extract_strings
1212
from ..utils.logger import get_structured_logger
1313

1414
bp = Blueprint("covidcast_meta", __name__)

‎src/server/endpoints/covidcast_nowcast.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from flask import Blueprint, request
22

3-
from .._query import execute_query, filter_integers, filter_strings
4-
from .._validate import (
3+
from .._params import (
54
extract_date,
65
extract_dates,
76
extract_integer,
8-
extract_strings,
7+
extract_strings
8+
)
9+
from .._query import execute_query, filter_integers, filter_strings
10+
from .._validate import (
911
require_all,
1012
require_any,
1113
)

‎src/server/endpoints/dengue_nowcast.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("dengue_nowcast", __name__)

‎src/server/endpoints/dengue_sensors.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask import Blueprint
22

33
from .._config import AUTH
4+
from .._params import extract_integers, extract_strings
45
from .._query import execute_query, QueryBuilder
5-
from .._validate import check_auth_token, extract_integers, extract_strings, require_all
6+
from .._validate import check_auth_token, require_all
67

78
# first argument is the endpoint name
89
bp = Blueprint("dengue_sensors", __name__)

‎src/server/endpoints/ecdc_ili.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("ecdc_ili", __name__)

‎src/server/endpoints/flusurv.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
bp = Blueprint("flusurv", __name__)
78

‎src/server/endpoints/fluview.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from flask import Blueprint
44

55
from .._config import AUTH
6-
from .._query import execute_queries, filter_integers, filter_strings
7-
from .._validate import (
8-
check_auth_token,
6+
from .._params import (
97
extract_integer,
108
extract_integers,
119
extract_strings,
10+
)
11+
from .._query import execute_queries, filter_integers, filter_strings
12+
from .._validate import (
13+
check_auth_token,
1214
require_all,
1315
)
1416

‎src/server/endpoints/fluview_clinicial.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
bp = Blueprint("fluview_clinical", __name__)
78

‎src/server/endpoints/gft.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("gft", __name__)

‎src/server/endpoints/ght.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask import Blueprint, request
22

33
from .._config import AUTH
4+
from .._params import extract_integers, extract_strings
45
from .._query import execute_query, QueryBuilder
5-
from .._validate import check_auth_token, extract_integers, extract_strings, require_all
6+
from .._validate import check_auth_token, require_all
67

78
# first argument is the endpoint name
89
bp = Blueprint("ght", __name__)

‎src/server/endpoints/kcdc_ili.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integer, extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("kcdc_ili", __name__)

‎src/server/endpoints/nidss_dengue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from flask import Blueprint
44

5+
from .._params import extract_integers, extract_strings
56
from .._query import execute_queries, filter_integers
6-
from .._validate import extract_integers, extract_strings, require_all
7+
from .._validate import require_all
78

89
# first argument is the endpoint name
910
bp = Blueprint("nidss_dengue", __name__)

‎src/server/endpoints/nidss_flu.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integer, extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("nidss_flu", __name__)

‎src/server/endpoints/norostat.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask import Blueprint, request
22

33
from .._config import AUTH
4+
from .._params import extract_integers
45
from .._query import execute_query, filter_integers, filter_strings
5-
from .._validate import check_auth_token, extract_integers, require_all
6+
from .._validate import check_auth_token, require_all
67

78
# first argument is the endpoint name
89
bp = Blueprint("norostat", __name__)

‎src/server/endpoints/nowcast.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("nowcast", __name__)

‎src/server/endpoints/paho_dengue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint
22

3+
from .._params import extract_integer, extract_integers, extract_strings
34
from .._query import execute_query, QueryBuilder
4-
from .._validate import extract_integer, extract_integers, extract_strings, require_all
5+
from .._validate import require_all
56

67
# first argument is the endpoint name
78
bp = Blueprint("paho_dengue", __name__)

‎src/server/endpoints/quidel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask import Blueprint
22

33
from .._config import AUTH
4+
from .._params import extract_integers, extract_strings
45
from .._query import execute_query, QueryBuilder
5-
from .._validate import check_auth_token, extract_integers, extract_strings, require_all
6+
from .._validate import check_auth_token, require_all
67

78
# first argument is the endpoint name
89
bp = Blueprint("quidel", __name__)

‎src/server/endpoints/sensors.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from flask import Blueprint
22

33
from .._config import AUTH, GRANULAR_SENSOR_AUTH_TOKENS, OPEN_SENSORS
4-
from .._validate import (
5-
require_all,
4+
from .._exceptions import EpiDataException
5+
from .._params import (
66
extract_strings,
77
extract_integers,
8-
resolve_auth_token,
98
)
109
from .._query import filter_strings, execute_query, filter_integers
11-
from .._exceptions import EpiDataException
10+
from .._validate import (
11+
require_all,
12+
resolve_auth_token,
13+
)
1214
from typing import List
1315

1416
# first argument is the endpoint name

‎src/server/endpoints/twitter.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from flask import Blueprint, request
22

33
from .._config import AUTH, NATION_REGION, REGION_TO_STATE
4+
from .._params import (
5+
extract_integers,
6+
extract_strings,
7+
)
48
from .._query import execute_queries, filter_dates, filter_integers, filter_strings
59
from .._validate import (
610
check_auth_token,
7-
extract_integers,
8-
extract_strings,
911
require_all,
1012
require_any,
1113
)

‎src/server/endpoints/wiki.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from flask import Blueprint, request
22

3+
from .._params import extract_integers, extract_strings
34
from .._query import execute_query, filter_dates, filter_integers, filter_strings
4-
from .._validate import extract_integers, extract_strings, require_all, require_any
5+
from .._validate import require_all, require_any
56

67
# first argument is the endpoint name
78
bp = Blueprint("wiki", __name__)

‎tests/server/test_params.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
# from flask.testing import FlaskClient
88
from delphi.epidata.server._common import app
99
from delphi.epidata.server._params import (
10+
extract_strings,
11+
extract_integers,
12+
extract_integer,
13+
extract_date,
14+
extract_dates,
1015
parse_geo_arg,
1116
parse_single_geo_arg,
1217
parse_source_signal_arg,
@@ -366,3 +371,128 @@ def test_parse_day_arg(self):
366371
self.assertRaises(ValidationFailedException, parse_day_arg, "time")
367372
with app.test_request_context("/?time=week:20121010"):
368373
self.assertRaises(ValidationFailedException, parse_day_arg, "time")
374+
375+
def test_extract_strings(self):
376+
with self.subTest("empty"):
377+
with app.test_request_context("/"):
378+
self.assertIsNone(extract_strings("s"))
379+
with self.subTest("single"):
380+
with app.test_request_context("/?s=a"):
381+
self.assertEqual(extract_strings("s"), ["a"])
382+
with self.subTest("multiple"):
383+
with app.test_request_context("/?s=a,b"):
384+
self.assertEqual(extract_strings("s"), ["a", "b"])
385+
with self.subTest("multiple param"):
386+
with app.test_request_context("/?s=a&s=b"):
387+
self.assertEqual(extract_strings("s"), ["a", "b"])
388+
with self.subTest("multiple param mixed"):
389+
with app.test_request_context("/?s=a&s=b,c"):
390+
self.assertEqual(extract_strings("s"), ["a", "b", "c"])
391+
392+
def test_extract_integer(self):
393+
with self.subTest("empty"):
394+
with app.test_request_context("/"):
395+
self.assertIsNone(extract_integer("s"))
396+
with self.subTest("single"):
397+
with app.test_request_context("/?s=1"):
398+
self.assertEqual(extract_integer("s"), 1)
399+
with self.subTest("not a number"):
400+
with app.test_request_context("/?s=a"):
401+
self.assertRaises(ValidationFailedException, lambda: extract_integer("s"))
402+
403+
def test_extract_integers(self):
404+
with self.subTest("empty"):
405+
with app.test_request_context("/"):
406+
self.assertIsNone(extract_integers("s"))
407+
with self.subTest("single"):
408+
with app.test_request_context("/?s=1"):
409+
self.assertEqual(extract_integers("s"), [1])
410+
with self.subTest("multiple"):
411+
with app.test_request_context("/?s=1,2"):
412+
self.assertEqual(extract_integers("s"), [1,2])
413+
with self.subTest("multiple param"):
414+
with app.test_request_context("/?s=1&s=2"):
415+
self.assertEqual(extract_integers("s"), [1,2])
416+
with self.subTest("multiple param mixed"):
417+
with app.test_request_context("/?s=1&s=2,3"):
418+
self.assertEqual(extract_integers("s"), [1, 2, 3])
419+
420+
with self.subTest("not a number"):
421+
with app.test_request_context("/?s=a"):
422+
self.assertRaises(ValidationFailedException, lambda: extract_integers("s"))
423+
424+
with self.subTest("simple range"):
425+
with app.test_request_context("/?s=1-2"):
426+
self.assertEqual(extract_integers("s"), [(1, 2)])
427+
with self.subTest("inverted range"):
428+
with app.test_request_context("/?s=2-1"):
429+
self.assertRaises(ValidationFailedException, lambda: extract_integers("s"))
430+
with self.subTest("single range"):
431+
with app.test_request_context("/?s=1-1"):
432+
self.assertEqual(extract_integers("s"), [1])
433+
434+
def test_extract_date(self):
435+
with self.subTest("empty"):
436+
with app.test_request_context("/"):
437+
self.assertIsNone(extract_date("s"))
438+
with self.subTest("single"):
439+
with app.test_request_context("/?s=2020-01-01"):
440+
self.assertEqual(extract_date("s"), 20200101)
441+
with app.test_request_context("/?s=20200101"):
442+
self.assertEqual(extract_date("s"), 20200101)
443+
with self.subTest("not a date"):
444+
with app.test_request_context("/?s=abc"):
445+
self.assertRaises(ValidationFailedException, lambda: extract_date("s"))
446+
447+
def test_extract_dates(self):
448+
with self.subTest("empty"):
449+
with app.test_request_context("/"):
450+
self.assertIsNone(extract_dates("s"))
451+
with self.subTest("single"):
452+
with app.test_request_context("/?s=20200101"):
453+
self.assertEqual(extract_dates("s"), [20200101])
454+
with self.subTest("multiple"):
455+
with app.test_request_context("/?s=20200101,20200102"):
456+
self.assertEqual(extract_dates("s"), [20200101, 20200102])
457+
with self.subTest("multiple param"):
458+
with app.test_request_context("/?s=20200101&s=20200102"):
459+
self.assertEqual(extract_dates("s"), [20200101, 20200102])
460+
with self.subTest("multiple param mixed"):
461+
with app.test_request_context("/?s=20200101&s=20200102,20200103"):
462+
self.assertEqual(extract_dates("s"), [20200101, 20200102, 20200103])
463+
with self.subTest("single iso"):
464+
with app.test_request_context("/?s=2020-01-01"):
465+
self.assertEqual(extract_dates("s"), [20200101])
466+
with self.subTest("multiple iso"):
467+
with app.test_request_context("/?s=2020-01-01,2020-01-02"):
468+
self.assertEqual(extract_dates("s"), [20200101, 20200102])
469+
with self.subTest("multiple param iso"):
470+
with app.test_request_context("/?s=2020-01-01&s=2020-01-02"):
471+
self.assertEqual(extract_dates("s"), [20200101, 20200102])
472+
with self.subTest("multiple param mixed iso"):
473+
with app.test_request_context("/?s=2020-01-01&s=2020-01-02,2020-01-03"):
474+
self.assertEqual(extract_dates("s"), [20200101, 20200102, 20200103])
475+
476+
with self.subTest("not a date"):
477+
with app.test_request_context("/?s=a"):
478+
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
479+
480+
with self.subTest("simple range"):
481+
with app.test_request_context("/?s=20200101-20200102"):
482+
self.assertEqual(extract_dates("s"), [(20200101, 20200102)])
483+
with self.subTest("inverted range"):
484+
with app.test_request_context("/?s=20200102-20200101"):
485+
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
486+
with self.subTest("single range"):
487+
with app.test_request_context("/?s=20200101-20200101"):
488+
self.assertEqual(extract_dates("s"), [20200101])
489+
490+
with self.subTest("simple range iso"):
491+
with app.test_request_context("/?s=2020-01-01:2020-01-02"):
492+
self.assertEqual(extract_dates("s"), [(20200101, 20200102)])
493+
with self.subTest("inverted range iso"):
494+
with app.test_request_context("/?s=2020-01-02:2020-01-01"):
495+
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
496+
with self.subTest("single range iso"):
497+
with app.test_request_context("/?s=2020-01-01:2020-01-01"):
498+
self.assertEqual(extract_dates("s"), [20200101])

‎tests/server/test_validate.py

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111
check_auth_token,
1212
require_all,
1313
require_any,
14-
extract_strings,
15-
extract_integers,
16-
extract_integer,
17-
extract_date,
18-
extract_dates
1914
)
2015
from delphi.epidata.server._exceptions import (
2116
ValidationFailedException,
@@ -110,129 +105,3 @@ def test_require_any(self):
110105
with self.subTest("one options given with is empty but ok"):
111106
with app.test_request_context("/?abc="):
112107
self.assertTrue(require_any("abc", empty=True))
113-
114-
def test_extract_strings(self):
115-
with self.subTest("empty"):
116-
with app.test_request_context("/"):
117-
self.assertIsNone(extract_strings("s"))
118-
with self.subTest("single"):
119-
with app.test_request_context("/?s=a"):
120-
self.assertEqual(extract_strings("s"), ["a"])
121-
with self.subTest("multiple"):
122-
with app.test_request_context("/?s=a,b"):
123-
self.assertEqual(extract_strings("s"), ["a", "b"])
124-
with self.subTest("multiple param"):
125-
with app.test_request_context("/?s=a&s=b"):
126-
self.assertEqual(extract_strings("s"), ["a", "b"])
127-
with self.subTest("multiple param mixed"):
128-
with app.test_request_context("/?s=a&s=b,c"):
129-
self.assertEqual(extract_strings("s"), ["a", "b", "c"])
130-
131-
def test_extract_integer(self):
132-
with self.subTest("empty"):
133-
with app.test_request_context("/"):
134-
self.assertIsNone(extract_integer("s"))
135-
with self.subTest("single"):
136-
with app.test_request_context("/?s=1"):
137-
self.assertEqual(extract_integer("s"), 1)
138-
with self.subTest("not a number"):
139-
with app.test_request_context("/?s=a"):
140-
self.assertRaises(ValidationFailedException, lambda: extract_integer("s"))
141-
142-
def test_extract_integers(self):
143-
with self.subTest("empty"):
144-
with app.test_request_context("/"):
145-
self.assertIsNone(extract_integers("s"))
146-
with self.subTest("single"):
147-
with app.test_request_context("/?s=1"):
148-
self.assertEqual(extract_integers("s"), [1])
149-
with self.subTest("multiple"):
150-
with app.test_request_context("/?s=1,2"):
151-
self.assertEqual(extract_integers("s"), [1,2])
152-
with self.subTest("multiple param"):
153-
with app.test_request_context("/?s=1&s=2"):
154-
self.assertEqual(extract_integers("s"), [1,2])
155-
with self.subTest("multiple param mixed"):
156-
with app.test_request_context("/?s=1&s=2,3"):
157-
self.assertEqual(extract_integers("s"), [1, 2, 3])
158-
159-
with self.subTest("not a number"):
160-
with app.test_request_context("/?s=a"):
161-
self.assertRaises(ValidationFailedException, lambda: extract_integers("s"))
162-
163-
with self.subTest("simple range"):
164-
with app.test_request_context("/?s=1-2"):
165-
self.assertEqual(extract_integers("s"), [(1, 2)])
166-
with self.subTest("inverted range"):
167-
with app.test_request_context("/?s=2-1"):
168-
self.assertRaises(ValidationFailedException, lambda: extract_integers("s"))
169-
with self.subTest("single range"):
170-
with app.test_request_context("/?s=1-1"):
171-
self.assertEqual(extract_integers("s"), [1])
172-
173-
def test_extract_date(self):
174-
with self.subTest("empty"):
175-
with app.test_request_context("/"):
176-
self.assertIsNone(extract_date("s"))
177-
with self.subTest("single"):
178-
with app.test_request_context("/?s=2020-01-01"):
179-
self.assertEqual(extract_date("s"), 20200101)
180-
with app.test_request_context("/?s=20200101"):
181-
self.assertEqual(extract_date("s"), 20200101)
182-
with self.subTest("not a date"):
183-
with app.test_request_context("/?s=abc"):
184-
self.assertRaises(ValidationFailedException, lambda: extract_date("s"))
185-
186-
def test_extract_dates(self):
187-
with self.subTest("empty"):
188-
with app.test_request_context("/"):
189-
self.assertIsNone(extract_dates("s"))
190-
with self.subTest("single"):
191-
with app.test_request_context("/?s=20200101"):
192-
self.assertEqual(extract_dates("s"), [20200101])
193-
with self.subTest("multiple"):
194-
with app.test_request_context("/?s=20200101,20200102"):
195-
self.assertEqual(extract_dates("s"), [20200101, 20200102])
196-
with self.subTest("multiple param"):
197-
with app.test_request_context("/?s=20200101&s=20200102"):
198-
self.assertEqual(extract_dates("s"), [20200101, 20200102])
199-
with self.subTest("multiple param mixed"):
200-
with app.test_request_context("/?s=20200101&s=20200102,20200103"):
201-
self.assertEqual(extract_dates("s"), [20200101, 20200102, 20200103])
202-
with self.subTest("single iso"):
203-
with app.test_request_context("/?s=2020-01-01"):
204-
self.assertEqual(extract_dates("s"), [20200101])
205-
with self.subTest("multiple iso"):
206-
with app.test_request_context("/?s=2020-01-01,2020-01-02"):
207-
self.assertEqual(extract_dates("s"), [20200101, 20200102])
208-
with self.subTest("multiple param iso"):
209-
with app.test_request_context("/?s=2020-01-01&s=2020-01-02"):
210-
self.assertEqual(extract_dates("s"), [20200101, 20200102])
211-
with self.subTest("multiple param mixed iso"):
212-
with app.test_request_context("/?s=2020-01-01&s=2020-01-02,2020-01-03"):
213-
self.assertEqual(extract_dates("s"), [20200101, 20200102, 20200103])
214-
215-
with self.subTest("not a date"):
216-
with app.test_request_context("/?s=a"):
217-
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
218-
219-
with self.subTest("simple range"):
220-
with app.test_request_context("/?s=20200101-20200102"):
221-
self.assertEqual(extract_dates("s"), [(20200101, 20200102)])
222-
with self.subTest("inverted range"):
223-
with app.test_request_context("/?s=20200102-20200101"):
224-
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
225-
with self.subTest("single range"):
226-
with app.test_request_context("/?s=20200101-20200101"):
227-
self.assertEqual(extract_dates("s"), [20200101])
228-
229-
with self.subTest("simple range iso"):
230-
with app.test_request_context("/?s=2020-01-01:2020-01-02"):
231-
self.assertEqual(extract_dates("s"), [(20200101, 20200102)])
232-
with self.subTest("inverted range iso"):
233-
with app.test_request_context("/?s=2020-01-02:2020-01-01"):
234-
self.assertRaises(ValidationFailedException, lambda: extract_dates("s"))
235-
with self.subTest("single range iso"):
236-
with app.test_request_context("/?s=2020-01-01:2020-01-01"):
237-
self.assertEqual(extract_dates("s"), [20200101])
238-

0 commit comments

Comments
 (0)
Please sign in to comment.