Skip to content

Commit 798b17f

Browse files
author
Julian Kath
committed
Add new functions for fetching changed series data and implement tests
1 parent cfcb13d commit 798b17f

4 files changed

Lines changed: 328 additions & 59 deletions

File tree

src/stats_can/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,23 @@
2727
"vectors_to_df",
2828
"scwds",
2929
"get_changed_cube_list",
30+
"get_changed_series_data_from_cube_pid_coord",
31+
"get_changed_series_data_from_vector",
3032
"get_changed_series_list",
3133
"get_cube_metadata",
34+
"get_data_from_cube_pid_coord_and_latest_n_periods",
3235
"get_series_info_from_cube_pid_coord",
3336
"get_series_info_from_vector",
3437
]
3538
from stats_can import sc, scwds, schemas
3639
from stats_can.sc import code_sets_to_df_dict, vectors_to_df, zip_table_to_dataframe
3740
from stats_can.scwds import (
3841
get_changed_cube_list,
42+
get_changed_series_data_from_cube_pid_coord,
43+
get_changed_series_data_from_vector,
3944
get_changed_series_list,
4045
get_cube_metadata,
46+
get_data_from_cube_pid_coord_and_latest_n_periods,
4147
get_series_info_from_cube_pid_coord,
4248
get_series_info_from_vector,
4349
)

src/stats_can/scwds.py

Lines changed: 124 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@
1313
SC_URL : str
1414
URL for the Statistics Canada REST api
1515
16-
TODO
17-
----
18-
Missing api implementations:
19-
GetChangedSeriesDataFromCubePidCoord
20-
GetChangedSeriesDataFromVector
21-
GetDataFromCubePidCoordAndLatestNPeriods
22-
GetFullTableDownloadSDMX
2316
"""
2417

2518
import datetime as dt
@@ -37,6 +30,7 @@
3730
from stats_can.helpers import (
3831
chunk_vectors,
3932
parse_tables,
33+
parse_vectors,
4034
pad_coordinate
4135
)
4236
from stats_can.schemas import (
@@ -51,6 +45,7 @@
5145
SC_URL = "https://www150.statcan.gc.ca/t1/wds/rest/"
5246
DEFAULT_TIMEOUT = 30
5347
_CHUNK_DELAY = 0.1
48+
_MAX_CHUNK = 250
5449
_USER_AGENT = f"stats_can/{version('stats_can')}"
5550

5651
T = TypeVar("T")
@@ -98,6 +93,26 @@ def _fetch_and_validate(
9893
raise RuntimeError(f"data came back weird. We should never get here: {data}")
9994

10095

96+
def _post_in_chunks(
97+
url: str, body: list[dict], schema: type[T]
98+
) -> list[T]:
99+
"""POST ``body`` to ``url`` in chunks of ``_MAX_CHUNK`` items.
100+
101+
Validates each chunk's response against ``schema`` and concatenates the
102+
results. Sleeps ``_CHUNK_DELAY`` seconds between chunks.
103+
"""
104+
chunks = [body[i : i + _MAX_CHUNK] for i in range(0, len(body), _MAX_CHUNK)]
105+
final_list: list[T] = []
106+
for i, chunk in enumerate(chunks):
107+
if i > 0:
108+
time.sleep(_CHUNK_DELAY)
109+
result = _fetch_and_validate(
110+
url, schema=schema, method="POST", json=chunk
111+
)
112+
final_list += result
113+
return final_list
114+
115+
101116
def get_changed_series_list() -> list[ChangedSeries]:
102117
"""[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a10-1)
103118
@@ -190,8 +205,6 @@ def get_series_info_from_cube_pid_coord(
190205
One :class:`SeriesInfo` per requested pair, in the order returned
191206
by the API.
192207
"""
193-
MAX_CHUNK = 250
194-
195208
if isinstance(pairs, tuple):
196209
pairs = [pairs]
197210

@@ -202,18 +215,9 @@ def get_series_info_from_cube_pid_coord(
202215
}
203216
for product_id, coord in pairs
204217
]
205-
206-
url = f"{SC_URL}getSeriesInfoFromCubePidCoord"
207-
chunks = [body[i : i + MAX_CHUNK] for i in range(0, len(body), MAX_CHUNK)]
208-
final_list: list[SeriesInfo] = []
209-
for i, chunk in enumerate(chunks):
210-
if i > 0:
211-
time.sleep(_CHUNK_DELAY)
212-
result = _fetch_and_validate(
213-
url, schema=SeriesInfo, method="POST", json=chunk
214-
)
215-
final_list += result
216-
return final_list
218+
return _post_in_chunks(
219+
f"{SC_URL}getSeriesInfoFromCubePidCoord", body, SeriesInfo
220+
)
217221

218222

219223
def get_series_info_from_vector(vectors: str | list[str]) -> list[SeriesInfo]:
@@ -229,42 +233,111 @@ def get_series_info_from_vector(vectors: str | list[str]) -> list[SeriesInfo]:
229233
:
230234
List of dicts containing metadata for each v#
231235
"""
232-
url = f"{SC_URL}getSeriesInfoFromVector"
233-
chunks = chunk_vectors(vectors)
234-
final_list = []
235-
for i, chunk in enumerate(chunks):
236-
if i > 0:
237-
time.sleep(_CHUNK_DELAY)
238-
vector_dict = [{"vectorId": v} for v in chunk]
239-
result = _fetch_and_validate(
240-
url, schema=SeriesInfo, method="POST", json=vector_dict
241-
)
242-
final_list += result
243-
return final_list
236+
body = [{"vectorId": v} for v in parse_vectors(vectors)]
237+
return _post_in_chunks(
238+
f"{SC_URL}getSeriesInfoFromVector", body, SeriesInfo
239+
)
244240

245241

246-
def get_changed_series_data_from_cube_pid_coord():
247-
"""Not implemented yet
242+
def get_changed_series_data_from_cube_pid_coord(
243+
pairs: tuple[str | int, str] | list[tuple[str | int, str]],
244+
) -> list[VectorData]:
245+
"""[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-1)
246+
247+
Get changed-series data for one or more (productId, coordinate) pairs.
248248
249-
[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-1)
249+
Parameters
250+
----------
251+
pairs
252+
One pair, or a list of pairs, where each pair is
253+
``(productId, coordinate)``. ``productId`` accepts the same forms
254+
as :func:`stats_can.helpers.parse_tables`. ``coordinate`` is the
255+
dot-delimited dimension member id string; it is right-padded with
256+
``.0`` to the 10 positions the API requires.
257+
258+
Returns
259+
-------
260+
:
261+
One :class:`VectorData` per requested pair, in the order returned
262+
by the API.
250263
"""
251-
pass
264+
if isinstance(pairs, tuple):
265+
pairs = [pairs]
252266

267+
body = [
268+
{
269+
"productId": parse_tables(product_id)[0],
270+
"coordinate": pad_coordinate(coord),
271+
}
272+
for product_id, coord in pairs
273+
]
274+
return _post_in_chunks(
275+
f"{SC_URL}getChangedSeriesDataFromCubePidCoord", body, VectorData
276+
)
253277

254-
def get_changed_series_data_from_vector():
255-
"""Not implemented yet
256278

257-
[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-2)
279+
def get_changed_series_data_from_vector(
280+
vectors: str | list[str],
281+
) -> list[VectorData]:
282+
"""[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-2)
283+
284+
Parameters
285+
----------
286+
vectors
287+
vector numbers to get changed data for
288+
289+
Returns
290+
-------
291+
:
292+
List of dicts containing changed data for each vector
258293
"""
259-
pass
294+
body = [{"vectorId": v} for v in parse_vectors(vectors)]
295+
return _post_in_chunks(
296+
f"{SC_URL}getChangedSeriesDataFromVector", body, VectorData
297+
)
298+
299+
300+
def get_data_from_cube_pid_coord_and_latest_n_periods(
301+
pairs: tuple[str | int, str] | list[tuple[str | int, str]],
302+
periods: int,
303+
) -> list[VectorData]:
304+
"""[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-3)
260305
306+
Get the latest ``periods`` reference periods of data for one or more
307+
(productId, coordinate) pairs.
261308
262-
def get_data_from_cube_pid_coord_and_latest_n_periods():
263-
"""Not implemented yet
309+
Parameters
310+
----------
311+
pairs
312+
One pair, or a list of pairs, where each pair is
313+
``(productId, coordinate)``. ``productId`` accepts the same forms
314+
as :func:`stats_can.helpers.parse_tables`. ``coordinate`` is the
315+
dot-delimited dimension member id string; it is right-padded with
316+
``.0`` to the 10 positions the API requires.
317+
periods
318+
number of periods (starting at latest) to retrieve data for; applied
319+
to every pair.
264320
265-
[api reference](https://www.statcan.gc.ca/eng/developers/wds/user-guide#a12-3)
321+
Returns
322+
-------
323+
:
324+
One :class:`VectorData` per requested pair, in the order returned
325+
by the API.
266326
"""
267-
pass
327+
if isinstance(pairs, tuple):
328+
pairs = [pairs]
329+
330+
body = [
331+
{
332+
"productId": parse_tables(product_id)[0],
333+
"coordinate": pad_coordinate(coord),
334+
"latestN": periods,
335+
}
336+
for product_id, coord in pairs
337+
]
338+
return _post_in_chunks(
339+
f"{SC_URL}getDataFromCubePidCoordAndLatestNPeriods", body, VectorData
340+
)
268341

269342

270343
def get_data_from_vectors_and_latest_n_periods(
@@ -284,17 +357,12 @@ def get_data_from_vectors_and_latest_n_periods(
284357
:
285358
List of dicts containing data for each vector
286359
"""
287-
url = f"{SC_URL}getDataFromVectorsAndLatestNPeriods"
288-
chunks = chunk_vectors(vectors)
289-
final_list = []
290-
for i, chunk in enumerate(chunks):
291-
if i > 0:
292-
time.sleep(_CHUNK_DELAY)
293-
periods_l = [periods for i in range(len(chunk))]
294-
json = [{"vectorId": v, "latestN": n} for v, n in zip(chunk, periods_l)]
295-
result = _fetch_and_validate(url, schema=VectorData, method="POST", json=json)
296-
final_list += result
297-
return final_list
360+
body = [
361+
{"vectorId": v, "latestN": periods} for v in parse_vectors(vectors)
362+
]
363+
return _post_in_chunks(
364+
f"{SC_URL}getDataFromVectorsAndLatestNPeriods", body, VectorData
365+
)
298366

299367

300368
def get_bulk_vector_data_by_range(

0 commit comments

Comments
 (0)