Skip to content

Commit

Permalink
added Gaarf-Report from_json()
Browse files Browse the repository at this point in the history
Change-Id: I5a6dfa3de5e9e7c9b4ee4b366afdd4d12fb210e1
  • Loading branch information
eladb authored and AVMarkin committed Nov 19, 2024
1 parent ca4e615 commit a7a01e4
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 1 deletion.
52 changes: 52 additions & 0 deletions py/gaarf/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,58 @@ def to_json(self) -> str:
"""
return json.dumps(self.to_list(row_type='dict'))

@classmethod
def from_json(cls, json_str: str) -> 'GaarfReport':
"""Creates a GaarfReport object from a JSON string.
Args:
json_str: JSON string representation of the data.
Returns:
GaarfReport.
Raises:
TypeError: If any value in the JSON data is not a supported type.
ValueError: If `data` is a list but not all dictionaries
have the same keys.
"""
data = json.loads(json_str)

def validate_value(value):
if not isinstance(value, parsers.GoogleAdsRowElement):
raise TypeError(
f'Unsupported type {type(value)} for value {value}. '
'Expected types: int, float, str, bool, list, or None.'
)
return value

# Case 1: `data` is a dictionary
if isinstance(data, dict):
column_names = list(data.keys())
if not data.values():
results = []
else:
results = [[validate_value(value) for value in data.values()]]

# Case 2: `data` is a list of dictionaries, each representing a row
elif isinstance(data, list):
column_names = list(data[0].keys()) if data else []
for row in data:
if not isinstance(row, dict):
raise TypeError('All elements in the list must be dictionaries.')
if list(row.keys()) != column_names:
raise ValueError(
'All dictionaries must have consistent keys in the same order.'
)
results = [
[validate_value(value) for value in row.values()] for row in data
]
else:
raise TypeError(
'Input JSON must be a dictionary or a list of dictionaries.'
)
return cls(results=results, column_names=column_names)

def get_value(
self, column_index: int = 0, row_index: int = 0
) -> parsers.GoogleAdsRowElement:
Expand Down
2 changes: 1 addition & 1 deletion py/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

setuptools.setup(
name='google-ads-api-report-fetcher',
version='1.15.3',
version='1.15.4',
python_requires='>3.8',
description=(
'Library for fetching reports from Google Ads API '
Expand Down
86 changes: 86 additions & 0 deletions py/tests/unit/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,92 @@ def test_hasattr_return_false_for_missing_value(self, multi_column_report):
False,
]

def test_from_json_with_single_row_dict_returns_gaarf_report(self):
json_str = '{"ad_group_id": 2, "campaign_id": 1}'
gaarf_report = report.GaarfReport.from_json(json_str)
expected_report = report.GaarfReport(
results=[[2, 1]], column_names=['ad_group_id', 'campaign_id']
)
assert gaarf_report == expected_report

def test_from_json_with_list_of_dicts_returns_gaarf_report(self):
json_str = (
'[{"ad_group_id": 2, "campaign_id": 1}, {"ad_group_id": 3, '
'"campaign_id": 2}]'
)
gaarf_report = report.GaarfReport.from_json(json_str)
expected_report = report.GaarfReport(
results=[[2, 1], [3, 2]], column_names=['ad_group_id', 'campaign_id']
)
assert gaarf_report == expected_report

def test_from_json_with_empty_list_returns_empty_report(self):
json_str = '[]'
gaarf_report = report.GaarfReport.from_json(json_str)
expected_report = report.GaarfReport(results=[], column_names=[])
assert gaarf_report == expected_report

def test_from_json_with_empty_dict_returns_empty_report(self):
json_str = '{}'
gaarf_report = report.GaarfReport.from_json(json_str)
expected_report = report.GaarfReport(results=[], column_names=[])
assert gaarf_report == expected_report

def test_from_json_with_inconsistent_keys_raises_value_error(self):
json_str = '[{"ad_group_id": 2}, {"campaign_id": 1}]'
with pytest.raises(
ValueError,
match='All dictionaries must have consistent keys in the same order.',
):
report.GaarfReport.from_json(json_str)

def test_from_json_with_unsupported_type_in_dict_raises_type_error(self):
json_str = '{"ad_group_id": {"nested": "value"}, "campaign_id": 1}'
with pytest.raises(
TypeError, match=r"Unsupported type <class 'dict'> for value"
):
report.GaarfReport.from_json(json_str)

def test_from_json_with_unsupported_type_in_list_raises_type_error(self):
json_str = (
'[{"ad_group_id": 2, "campaign_id": {"ad_group_id": 2, '
'"campaign_id": 1}}]'
)
with pytest.raises(
TypeError,
match=r"Unsupported type <class 'dict'> for value {'ad_group_id': 2, "
r"'campaign_id': 1}. Expected types: int, float, str, bool, list, or "
r'None.',
):
report.GaarfReport.from_json(json_str)

def test_from_json_with_inconsistent_column_order_raises_value_error(self):
json_str = (
'[{"ad_group_id": 2, "campaign_id": 1}, {"campaign_id": 2, '
'"ad_group_id": 3}]'
)

with pytest.raises(
ValueError,
match='All dictionaries must have consistent keys in the same order.',
):
report.GaarfReport.from_json(json_str)

def test_from_json_with_non_dict_or_list_raises_type_error(self):
json_str = '"invalid_data"'
with pytest.raises(
TypeError,
match='Input JSON must be a dictionary or a list of dictionaries.',
):
report.GaarfReport.from_json(json_str)

def test_from_json_with_non_dict_elements_in_list_raises_type_error(self):
json_str = '[{"ad_group_id": 2}, 123]'
with pytest.raises(
TypeError, match='All elements in the list must be dictionaries.'
):
report.GaarfReport.from_json(json_str)

class TestGaarfReportMisc:
def test_get_report_length(self, multi_column_report):
assert len(multi_column_report) == len(multi_column_report.results)
Expand Down

0 comments on commit a7a01e4

Please sign in to comment.