1313SC_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
2518import datetime as dt
3730from stats_can .helpers import (
3831 chunk_vectors ,
3932 parse_tables ,
33+ parse_vectors ,
4034 pad_coordinate
4135)
4236from stats_can .schemas import (
5145SC_URL = "https://www150.statcan.gc.ca/t1/wds/rest/"
5246DEFAULT_TIMEOUT = 30
5347_CHUNK_DELAY = 0.1
48+ _MAX_CHUNK = 250
5449_USER_AGENT = f"stats_can/{ version ('stats_can' )} "
5550
5651T = 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+
101116def 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
219223def 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
270343def 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
300368def get_bulk_vector_data_by_range (
0 commit comments