garf-core 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
garf_core/__init__.py CHANGED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = '0.1.1'
15
+ __version__ = '0.1.2'
garf_core/parsers.py CHANGED
@@ -32,21 +32,26 @@ ApiRowElement: TypeAlias = Union[int, float, str, bool, list, None]
32
32
  class BaseParser(abc.ABC):
33
33
  """An interface for all parsers to implement."""
34
34
 
35
+ def __init__(
36
+ self, query_specification: query_editor.BaseQueryElements
37
+ ) -> None:
38
+ """Initializes BaseParser."""
39
+ self.query_spec = query_specification
40
+
35
41
  def parse_response(
36
42
  self,
37
43
  response: api_clients.GarfApiResponse,
38
- query_specification: query_editor.BaseQueryElements,
39
44
  ) -> list[list[ApiRowElement]]:
40
45
  """Parses response."""
41
46
  if not response.results:
42
47
  return [[]]
43
48
  results = []
44
49
  for result in response.results:
45
- results.append(self.parse_row(result, query_specification))
50
+ results.append(self.parse_row(result))
46
51
  return results
47
52
 
48
53
  @abc.abstractmethod
49
- def parse_row(self, row, query_specification):
54
+ def parse_row(self, row):
50
55
  """Parses single row from response."""
51
56
 
52
57
 
@@ -57,7 +62,6 @@ class ListParser(BaseParser):
57
62
  def parse_row(
58
63
  self,
59
64
  row: list,
60
- query_specification: query_editor.BaseQueryElements,
61
65
  ) -> list[list[ApiRowElement]]:
62
66
  return row
63
67
 
@@ -69,12 +73,11 @@ class DictParser(BaseParser):
69
73
  def parse_row(
70
74
  self,
71
75
  row: list,
72
- query_specification: query_editor.BaseQueryElements,
73
76
  ) -> list[list[ApiRowElement]]:
74
77
  if not isinstance(row, Mapping):
75
78
  raise GarfParserError
76
79
  result = []
77
- for field in query_specification.fields:
80
+ for field in self.query_spec.fields:
78
81
  result.append(self.get_nested_field(row, field))
79
82
  return result
80
83
 
garf_core/report.py CHANGED
@@ -45,25 +45,25 @@ class GarfReport:
45
45
 
46
46
  def __init__(
47
47
  self,
48
- results: Sequence[Sequence[parsers.ApiRowElement]],
49
- column_names: Sequence[str],
48
+ results: Sequence[Sequence[parsers.ApiRowElement]] | None = None,
49
+ column_names: Sequence[str] | None = None,
50
50
  results_placeholder: Sequence[Sequence[parsers.ApiRowElement]]
51
51
  | None = None,
52
- query_specification: query_editor.BaseQuerySpecification | None = None,
52
+ query_specification: query_editor.BaseQueryElements | None = None,
53
53
  auto_convert_to_scalars: bool = True,
54
54
  ) -> None:
55
55
  """Initializes GarfReport from API response.
56
56
 
57
57
  Args:
58
- results: Contains data from Ads API in a form of nested list
59
- column_names: Maps in each element in sublist of results to name.
60
- results_placeholder: Optional placeholder values for missing results.
61
- query_specification: Specification used to get data from Ads API.
62
- auto_convert_to_scalars: Whether to simplify slicing operations.
58
+ results: Contains data from Ads API in a form of nested list
59
+ column_names: Maps in each element in sublist of results to name.
60
+ results_placeholder: Optional placeholder values for missing results.
61
+ query_specification: Specification used to get data from Ads API.
62
+ auto_convert_to_scalars: Whether to simplify slicing operations.
63
63
  """
64
- self.results = results
65
- self.column_names = column_names
66
- self._multi_column_report = len(column_names) > 1
64
+ self.results = results or []
65
+ self.column_names = column_names or []
66
+ self._multi_column_report = len(column_names) > 1 if column_names else False
67
67
  if results_placeholder:
68
68
  self.results_placeholder = list(results_placeholder)
69
69
  else:
@@ -238,6 +238,8 @@ class GarfReport:
238
238
  Raises:
239
239
  GarfReportError: If row or column index are out of bounds.
240
240
  """
241
+ if not self:
242
+ raise GarfReportError('Cannot get value from an empty report')
241
243
  if column_index >= len(self.column_names):
242
244
  raise GarfReportError(
243
245
  'Column %d of report is not found; report contains only %d columns.',
@@ -298,6 +300,10 @@ class GarfReport:
298
300
  Raises:
299
301
  GarfReportError: When incorrect column_name specified.
300
302
  """
303
+ if not self:
304
+ if isinstance(key, (MutableSequence, str)):
305
+ raise GarfReportError(f"Cannot get '{key}' from an empty report")
306
+ raise GarfReportError('Cannot get element from an empty report')
301
307
  if isinstance(key, (MutableSequence, str)):
302
308
  return self._get_columns_slice(key)
303
309
  return self._get_rows_slice(key)
@@ -342,6 +348,8 @@ class GarfReport:
342
348
  Raises:
343
349
  GarfReportError: When incorrect column_name specified.
344
350
  """
351
+ if not self:
352
+ return self
345
353
  if isinstance(key, str):
346
354
  key = [key]
347
355
  if set(key).issubset(set(self.column_names)):
@@ -356,15 +364,13 @@ class GarfReport:
356
364
  results.append(rows)
357
365
  # TODO: propagate placeholders and query specification to new report
358
366
  return GarfReport(results, key)
359
- non_existing_keys = set(key).intersection(set(self.column_names))
367
+ non_existing_keys = set(key).difference(set(self.column_names))
360
368
  if len(non_existing_keys) > 1:
361
- message = (
362
- f"Columns '{', '.join(list(non_existing_keys))}' "
363
- 'cannot be found in the report'
364
- )
365
- message = (
366
- f"Column '{non_existing_keys.pop()}' " 'cannot be found in the report'
367
- )
369
+ missing_columns = ', '.join(list(non_existing_keys))
370
+ else:
371
+ missing_columns = non_existing_keys.pop()
372
+
373
+ message = f"Columns '{missing_columns}' cannot be found in the report"
368
374
  raise GarfReportError(message)
369
375
 
370
376
  def __eq__(self, other) -> bool:
@@ -39,6 +39,17 @@ class ApiReportFetcherError(exceptions.GarfError):
39
39
  """Base exception for all ApiReportFetchers."""
40
40
 
41
41
 
42
+ class MissingApiReportFetcherError(ApiReportFetcherError):
43
+ """Exception for not found report fetcher."""
44
+
45
+ def __init__(self, source: str) -> None:
46
+ """Initializes MissingApiReportFetcherError."""
47
+ self.source = source
48
+
49
+ def __str__(self) -> str:
50
+ return f'No fetcher available for the source "{self.source}"'
51
+
52
+
42
53
  class ApiReportFetcher:
43
54
  """Class responsible for getting data from report API.
44
55
 
@@ -69,7 +80,7 @@ class ApiReportFetcher:
69
80
  Mapping between query name and function for generating GarfReport.
70
81
  """
71
82
  self.api_client = api_client
72
- self.parser = parser()
83
+ self.parser = parser
73
84
  self.query_specification_builder = query_specification_builder
74
85
  self.query_args = kwargs
75
86
  self.builtin_queries = builtin_queries or {}
@@ -135,7 +146,7 @@ class ApiReportFetcher:
135
146
  return builtin_report(self, **kwargs)
136
147
 
137
148
  response = self.api_client.get_response(query, **kwargs)
138
- parsed_response = self.parser.parse_response(response, query)
149
+ parsed_response = self.parser(query).parse_response(response)
139
150
  return report.GarfReport(
140
151
  results=parsed_response, column_names=query.column_names
141
152
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: garf-core
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Abstracts fetching data from API based on provided SQL-like query.
5
5
  Author-email: "Google Inc. (gTech gPS CSE team)" <no-reply@google.com>, Andrei Markin <andrey.markin.ppc@gmail.com>
6
6
  License: Apache 2.0
@@ -1,16 +1,16 @@
1
- garf_core/__init__.py,sha256=5D5GNZp_18xDdFHuq7q1jn0HbR0-HD0s4vn0qzAAGKo,598
1
+ garf_core/__init__.py,sha256=GIvTQD8k9INUEIQO5HBfLShtzbins5jjqQfi0z2J9h0,598
2
2
  garf_core/api_clients.py,sha256=k4vjFsA3JOx7Hp_pXIQNwzMXqq4u4dDcz-ddkJC0JhI,4651
3
3
  garf_core/base_query.py,sha256=ZDAw2ojmismXRO0HXEvKDukpS7OAc7390LnM8kvCSCY,1201
4
4
  garf_core/exceptions.py,sha256=Gzvkl2M-rA_XQRAMd3CC62KHeFQE_b6uby0fD0pouw4,1269
5
- garf_core/parsers.py,sha256=Uj7aT7roYUofivrBKVKWfcSeG37oYGGLIJ-op2C3hZc,3238
5
+ garf_core/parsers.py,sha256=-178UL2hbrC83GkMqAPgrmpGpJitR7T9xT3OgJ8w16Y,3189
6
6
  garf_core/query_editor.py,sha256=lA1aMFc_1wo6e2CyVISarnZxtH2jnKkuZfJecQqslDg,17191
7
- garf_core/report.py,sha256=2z4tUR5mg29CkwodGaFIXs8Vpo2DCyyzwJWXhBrT0R4,19910
8
- garf_core/report_fetcher.py,sha256=o6YT_o0t9EPutiJk9R637lQabkHSaOrzoF2ciOmpQPA,4560
7
+ garf_core/report.py,sha256=O0SPxgVQckxi_QuD3vy1h_1mi5aMjQNw0LYP-jgN2Do,20262
8
+ garf_core/report_fetcher.py,sha256=dgErrkRv1bdPN0npX3atZbJrOpzy9OmoPC38e2FpBSQ,4883
9
9
  garf_core/fetchers/__init__.py,sha256=_cSjg1D5RhUKxaVeVbaDdb8AAoI9glKJXgN5H4qXFkw,783
10
10
  garf_core/fetchers/fake.py,sha256=fgJjxuHyd6EIUflUtj8r_HfaMS1YTDrOqDlaj6Kvbjs,2584
11
11
  garf_core/fetchers/rest.py,sha256=-5B2-Ck_t3hG99ym59AKwlzctiDxRFI2Nnc8STBxRDo,2201
12
- garf_core-0.1.1.dist-info/METADATA,sha256=OY4D0XX-hTCKypRrrea9bBYuqpR87Z58Y5WK35X31zo,2443
13
- garf_core-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- garf_core-0.1.1.dist-info/entry_points.txt,sha256=u4h-ujHO1hbxVXRQzwcC4ftju9_KBYtq5mCLKEBHMj0,69
15
- garf_core-0.1.1.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
16
- garf_core-0.1.1.dist-info/RECORD,,
12
+ garf_core-0.1.2.dist-info/METADATA,sha256=dGX17SeAetNBUlagvqWDGHX4yzIura36Bl1CP0Bd0OU,2443
13
+ garf_core-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ garf_core-0.1.2.dist-info/entry_points.txt,sha256=u4h-ujHO1hbxVXRQzwcC4ftju9_KBYtq5mCLKEBHMj0,69
15
+ garf_core-0.1.2.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
16
+ garf_core-0.1.2.dist-info/RECORD,,