Skip to content

Commit

Permalink
Merge pull request #3 from yuhui/2.0.2
Browse files Browse the repository at this point in the history
update to 2.0.2
  • Loading branch information
yuhui authored Dec 29, 2024
2 parents 23b8a26 + 74abbe7 commit 205285e
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 67 deletions.
2 changes: 1 addition & 1 deletion singstat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .types import Url

NAME = 'singstat'
VERSION = '2.0.1' # Production
VERSION = '2.0.2' # Production
# VERSION = f'{VERSION}.{datetime.now().strftime("%Y%m%d%H%M")}' # Development
AUTHOR = 'Yuhui'
AUTHOR_EMAIL = 'yuhuibc@gmail.com'
Expand Down
12 changes: 6 additions & 6 deletions singstat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def metadata(self, resource_id: str) -> MetadataDict:
metadata_endpoint = f'{METADATA_ENDPOINT}/{resource_id}'
metadata = self.send_request(metadata_endpoint)

data_count = metadata['data_count']
records = metadata['data']['records']
data_count = metadata['DataCount']
records = metadata['Data']['records']

if data_count == 1 and len(records) == 0:
warn('Empty data set returned', RuntimeWarning)
Expand Down Expand Up @@ -102,8 +102,8 @@ def resource_id(self, **kwargs: Any) -> ResourceIdDict:

resources = self.send_request(RESOURCE_ID_ENDPOINT, params)

data_count = resources['data_count']
total = resources['data']['total']
data_count = resources['DataCount']
total = resources['Data']['total']

if data_count == 1 and total == 0:
warn('Empty data set returned', RuntimeWarning)
Expand Down Expand Up @@ -189,8 +189,8 @@ def tabledata(self, resource_id: str, **kwargs: Any) -> TabledataDict:
tabledata_endpoint = f'{TABLEDATA_ENDPOINT}/{resource_id}'
tabledata = self.send_request(tabledata_endpoint, params)

data_count = tabledata['data_count']
rows = tabledata['data']['row']
data_count = tabledata['DataCount']
rows = tabledata['Data']['row']

if data_count == 1 and len(rows) == 0:
warn('Empty data set returned', RuntimeWarning)
Expand Down
32 changes: 17 additions & 15 deletions singstat/singstat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from requests import codes as requests_codes
from requests.adapters import HTTPAdapter, Retry
from requests_cache import CachedSession
from requests_cache import BaseCache, CachedSession
from typeguard import check_type, typechecked

from .constants import (
Expand All @@ -44,6 +44,11 @@ class SingStat:
- Cache to expire after 12 hours.
- User-agent header.
:param cache_backend: Cache backend name or instance to use. Refer to \
https://requests-cache.readthedocs.io/en/stable/user_guide/backends.html \
for more information and allowed values. Defaults to "sqlite".
:type cache_backend: str | BaseCache
:param is_test_api: Whether to use SingStat's test API. If this is set to \
True, then ``isTestApi=true`` is added to the parameters when calling \
``send_request()``. Defaults to False.
Expand All @@ -53,19 +58,24 @@ class SingStat:
is_test_api: bool

@typechecked
def __init__(self, is_test_api: bool=False) -> None:
def __init__(
self,
cache_backend: str | BaseCache='sqlite',
is_test_api: bool=False,
) -> None:
"""Constructor method"""
self.is_test_api = is_test_api

expire_after = CACHE_TWELVE_HOURS
retries = Retry(
total=5,
backoff_factor=0.1,
status_forcelist=[500, 502, 503, 504]
)

expire_after = CACHE_TWELVE_HOURS
self.session = CachedSession(
CACHE_NAME,
backend=cache_backend,
expire_after=expire_after,
stale_if_error=False,
)
Expand Down Expand Up @@ -140,7 +150,6 @@ def sanitise_data(
2-value tuple. The ``dict`` keys are: "between", \
"dataLastUpdated", "dateGenerated", "limit", "offset", "rowNo", \
"total"
- ``dict`` keys: convert to use Python's snake_case.
:param value: Value to sanitise.
:type value: Any
Expand All @@ -163,16 +172,10 @@ def sanitise_data(
elif iterate and isinstance(value, dict):
sanitised_value = {}
for k, v in value.items():
# Convert dict key to snake_case.
# Ref: https://www.geeksforgeeks.org/python-program-to-convert-camel-case-string-to-snake-case/
key = ''.join(
['_' + i.lower() if i.isupper() else i for i in k]
).lstrip('_')

if k in DATA_KEYS_TO_SANITISE or isinstance(v, (dict, list)):
sanitised_value[key] = self.sanitise_data(v, iterate=iterate)
sanitised_value[k] = self.sanitise_data(v, iterate=iterate)
else:
sanitised_value[key] = v
sanitised_value[k] = v
elif isinstance(value, str):
try:
# pylint: disable=broad-exception-caught
Expand Down Expand Up @@ -255,11 +258,10 @@ def send_request(
except ValueError:
pass

data_count = response_json['DataCount'] \
if 'DataCount' in response_json else 0

data = self.sanitise_data(response_json) if sanitise else response_json

data_count = response_json['DataCount'] \
if 'DataCount' in response_json else 0
if data_count == 0:
raise APIError('No data records returned', data=response_json)

Expand Down
83 changes: 43 additions & 40 deletions singstat/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Url: TypeAlias = str
"""URL of link."""

# Resource ID

class _ResourceIdDataRecordDict(TypedDict):
"""Type definition for _ResourceIdDataDict
Expand All @@ -34,16 +35,16 @@ class _ResourceIdDataRecordDict(TypedDict):
"""
id: NotRequired[str]
"""ID"""
table_type: NotRequired[str]
tableType: NotRequired[str]
"""Table type"""
title: NotRequired[str]
"""Title"""

class _ResourceIdDataDict(TypedDict):
"""Type definition for ResourceIdDict"""
generated_by: str
generatedBy: str
"""Generated by"""
date_generated: date
dateGenerated: date
"""Data generated"""
total: int
"""Total"""
Expand All @@ -52,23 +53,24 @@ class _ResourceIdDataDict(TypedDict):

class ResourceIdDict(TypedDict):
"""Type definition for resource_id()"""
data: _ResourceIdDataDict
Data: _ResourceIdDataDict
"""Data"""
data_count: int
DataCount: int
"""Data count"""
status_code: int
StatusCode: int
"""Status code"""
message: str
Message: str
"""Message"""

# Metadata

class _MetadataDataRecordsRowDict(TypedDict):
"""Type definition for _MetadataDataRecordsDict"""
series_no: str
seriesNo: str
"""Series number"""
row_text: str
rowText: str
"""Row text"""
uo_m: str
uoM: str
"""Unit of measurement"""
footnote: str
"""Footnote"""
Expand All @@ -85,15 +87,15 @@ class _MetadataDataRecordsDict(TypedDict):
"""Title"""
frequency: NotRequired[str]
"""Frequency"""
data_source: NotRequired[str]
dataSource: NotRequired[str]
"""Data source"""
footnote: NotRequired[str]
"""Footnote"""
data_last_updated: NotRequired[date]
dataLastUpdated: NotRequired[date]
"""Data last updated"""
start_period: NotRequired[str]
startPeriod: NotRequired[str]
"""Start period"""
end_period: NotRequired[str]
endPeriod: NotRequired[str]
"""End period"""
total: NotRequired[int]
"""Total"""
Expand All @@ -102,24 +104,25 @@ class _MetadataDataRecordsDict(TypedDict):

class _MetadataDataDict(TypedDict):
"""Type definition for MetadataDict"""
generated_by: str
generatedBy: str
"""Generated by"""
date_generated: date
dateGenerated: date
"""Date generated"""
records: _MetadataDataRecordsDict
"""Records"""

class MetadataDict(TypedDict):
"""Type definition for metadata()"""
data: _MetadataDataDict
Data: _MetadataDataDict
"""Data"""
data_count: int
DataCount: int
"""Data count"""
status_code: int
StatusCode: int
"""Status code"""
message: str
Message: str
"""Message"""

# Tabledata

class _TabledataDataRowColumnDict(TypedDict):
"""Type definition for _TabledataDataRowColumnColumnDict, \
Expand All @@ -142,11 +145,11 @@ class _TabledataDataRowColumnColumnDict(TypedDict):

class _TabledataDataTimeseriesRowDict(TypedDict):
"""Type definition for _TabledataDataTimeseriesDict"""
series_no: str
seriesNo: str
"""Series number"""
row_text: str
rowText: str
"""Row text"""
uo_m: str
uoM: str
"""Unit of measurement"""
footnote: str
"""Footnote"""
Expand All @@ -157,11 +160,11 @@ class _TabledataDataCrossSectionalMultiDimensionalCubeRowDict(TypedDict):
"""Type definition for \
_TabledataDataCrossSectionalMultiDimensionalCubeDict
"""
row_no: int
rowNo: int
"""Row number"""
row_text: str
rowText: str
"""Row text"""
uo_m: str
uoM: str
"""Unit of measurement"""
footnote: str
"""Footnote"""
Expand All @@ -186,19 +189,19 @@ class _TabledataDataTimeseriesDict(TypedDict):
"""Frequency"""
datasource: NotRequired[str]
"""Data source"""
generated_by: NotRequired[str]
generatedBy: NotRequired[str]
"""Generated by"""
data_last_updated: NotRequired[date]
dataLastUpdated: NotRequired[date]
"""Data last updated"""
date_generated: NotRequired[date]
dateGenerated: NotRequired[date]
"""Date generated"""
offset: NotRequired[int | None]
"""Offset"""
limit: NotRequired[int]
"""Limit"""
sort_by: NotRequired[str | None]
sortBy: NotRequired[str | None]
"""Sort by"""
time_filter: NotRequired[str | None]
timeFilter: NotRequired[str | None]
"""Time filter"""
between: NotRequired[tuple[int, ...] | None]
"""Between"""
Expand All @@ -215,21 +218,21 @@ class _TabledataDataCrossSectionalMultiDimensionalCubeDict(TypedDict):
"""
id: NotRequired[str]
"""ID"""
table_type: NotRequired[str]
tableType: NotRequired[str]
"""Table type"""
title: NotRequired[str]
"""Title"""
footnote: NotRequired[str]
"""Footnote"""
frequency: NotRequired[str]
"""Frequency"""
data_source: NotRequired[str]
dataSource: NotRequired[str]
"""Data source"""
generated_by: NotRequired[str]
generatedBy: NotRequired[str]
"""Generated by"""
data_last_updated: NotRequired[date]
dataLastUpdated: NotRequired[date]
"""Data last updated"""
date_generated: NotRequired[date]
dateGenerated: NotRequired[date]
"""Date generated"""
offset: NotRequired[int | None]
"""Offset"""
Expand All @@ -244,13 +247,13 @@ class _TabledataDataCrossSectionalMultiDimensionalCubeDict(TypedDict):

class TabledataDict(TypedDict):
"""Type definition for tabledata()"""
data: _TabledataDataTimeseriesDict | _TabledataDataCrossSectionalMultiDimensionalCubeDict
Data: _TabledataDataTimeseriesDict | _TabledataDataCrossSectionalMultiDimensionalCubeDict
"""Data"""
data_count: int
DataCount: int
"""Data count"""
status_code: int
StatusCode: int
"""Status code"""
message: str
Message: str
"""Message"""

__all__ = [
Expand Down
4 changes: 2 additions & 2 deletions tests/mocks/api_response_empty_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class APIResponseEmptyMetadata:
@staticmethod
def json():
return {
'data': {
'Data': {
'generatedBy': 'SingStat Table Builder',
'dateGenerated': date(2024, 12, 1),
'records': {},
Expand All @@ -40,7 +40,7 @@ class APIResponseEmptyTabledata:
@staticmethod
def json():
return {
'data': {
'Data': {
'generatedBy': 'SingStat Table Builder',
'dateGenerated': date(2024, 12, 1),
'row': [],
Expand Down
1 change: 0 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from typeguard import check_type

from singstat import Client
from singstat.exceptions import APIError
from singstat.types import MetadataDict, ResourceIdDict, TabledataDict

from .mocks.api_response_empty_data import (
Expand Down
4 changes: 2 additions & 2 deletions tests/test_singstat.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ def test_build_params_with_bad_inputs(client, original_params, default_params):
'value_date': '1/7/2019',
'value_list': [1, '2', {
'between': 'foo,77',
'data_last_updated': date(2021, 3, 12),
'dataLastUpdated': date(2021, 3, 12),
}],
'value_dict': {
'key1': '316',
'between': (45, 89),
'date_generated': date(2024, 12, 1),
'dateGenerated': date(2024, 12, 1),
},
},
),
Expand Down

0 comments on commit 205285e

Please sign in to comment.