garf-core 0.0.8__py3-none-any.whl → 0.0.9__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.0.8'
15
+ __version__ = '0.0.9'
garf_core/api_clients.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Google LLC
1
+ # Copyright 2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
garf_core/base_query.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
garf_core/exceptions.py CHANGED
@@ -29,31 +29,3 @@ from __future__ import annotations
29
29
 
30
30
  class GarfError(Exception):
31
31
  """Base exception."""
32
-
33
-
34
- class GarfQueryException(GarfError):
35
- """Base exception for Garf queries."""
36
-
37
-
38
- class GarfParserException(GarfError):
39
- """Base exception for Garf parsers."""
40
-
41
-
42
- class GarfCustomizerException(GarfParserException):
43
- """Specifies incorrect customizer."""
44
-
45
-
46
- class GarfVirtualColumnException(GarfParserException):
47
- """Specifies incorrect virtual column type."""
48
-
49
-
50
- class GarfFieldException(GarfQueryException):
51
- """Specifies incorrect Google Ads API field."""
52
-
53
-
54
- class GarfMacroException(GarfQueryException):
55
- """Specifies incorrect macro in Garf query."""
56
-
57
-
58
- class GarfResourceException(GarfQueryException):
59
- """Specifies incorrect resource name in Google Ads API."""
garf_core/parsers.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -11,11 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Module for defining various parsing strategy for GoogleAdsRow elements.
15
-
16
- GoogleAdsRowParser parses a single GoogleAdsRow and applies different parsing
17
- strategies to each element of the row.
18
- """
14
+ """Module for defining various parsing strategy for API response."""
19
15
 
20
16
  from __future__ import annotations
21
17
 
@@ -24,7 +20,7 @@ import contextlib
24
20
  import functools
25
21
  import operator
26
22
  from collections.abc import Mapping, MutableSequence
27
- from typing import Union
23
+ from typing import Any, Union
28
24
 
29
25
  from typing_extensions import TypeAlias, override
30
26
 
@@ -34,44 +30,56 @@ ApiRowElement: TypeAlias = Union[int, float, str, bool, list, None]
34
30
 
35
31
 
36
32
  class BaseParser(abc.ABC):
37
- @abc.abstractmethod
33
+ """An interface for all parsers to implement."""
34
+
38
35
  def parse_response(
39
- self, response: api_clients.GarfApiResponse
36
+ self,
37
+ response: api_clients.GarfApiResponse,
38
+ query_specification: query_editor.BaseQueryElements,
40
39
  ) -> list[list[ApiRowElement]]:
41
40
  """Parses response."""
41
+ if not response.results:
42
+ return [[]]
43
+ results = []
44
+ for result in response.results:
45
+ results.append(self.parse_row(result, query_specification))
46
+ return results
47
+
48
+ @abc.abstractmethod
49
+ def parse_row(self, row, query_specification):
50
+ """Parses single row from response."""
42
51
 
43
52
 
44
53
  class ListParser(BaseParser):
54
+ """Returns API results as is."""
55
+
45
56
  @override
46
- def parse_response(
57
+ def parse_row(
47
58
  self,
48
- response: api_clients.GarfApiResponse,
59
+ row: list,
49
60
  query_specification: query_editor.BaseQueryElements,
50
61
  ) -> list[list[ApiRowElement]]:
51
- del query_specification
52
- return response.results
62
+ return row
53
63
 
54
64
 
55
65
  class DictParser(BaseParser):
66
+ """Extracts nested dict elements."""
67
+
56
68
  @override
57
- def parse_response(
69
+ def parse_row(
58
70
  self,
59
- response: api_clients.GarfApiResponse,
71
+ row: list,
60
72
  query_specification: query_editor.BaseQueryElements,
61
73
  ) -> list[list[ApiRowElement]]:
62
- if not response.results:
63
- return [[]]
64
- if not isinstance(response.results[0], Mapping):
65
- return GarfParserError
66
- results = []
67
- for result in response.results:
68
- row = []
69
- for field in query_specification.fields:
70
- row.append(self.get_nested_field(result, field))
71
- results.append(row)
72
- return results
73
-
74
- def get_nested_field(self, dictionary, key):
74
+ if not isinstance(row, Mapping):
75
+ raise GarfParserError
76
+ result = []
77
+ for field in query_specification.fields:
78
+ result.append(self.get_nested_field(row, field))
79
+ return result
80
+
81
+ def get_nested_field(self, dictionary: dict[str, Any], key: str):
82
+ """Returns nested fields from a dictionary."""
75
83
  key = key.split('.')
76
84
  try:
77
85
  return functools.reduce(operator.getitem, key, dictionary)
@@ -80,7 +88,11 @@ class DictParser(BaseParser):
80
88
 
81
89
 
82
90
  class NumericConverterDictParser(DictParser):
83
- def get_nested_field(self, dictionary, key):
91
+ """Extracts nested dict elements with numerical conversions."""
92
+
93
+ def get_nested_field(self, dictionary: dict[str, Any], key: str):
94
+ """Extract nested field with int/float conversion."""
95
+
84
96
  def convert_field(value):
85
97
  for type_ in (int, float):
86
98
  with contextlib.suppress(ValueError):
garf_core/query_editor.py CHANGED
@@ -29,6 +29,30 @@ from typing_extensions import Self
29
29
  from garf_core import exceptions
30
30
 
31
31
 
32
+ class GarfQueryError(exceptions.GarfError):
33
+ """Base exception for Garf queries."""
34
+
35
+
36
+ class GarfCustomizerError(GarfQueryError):
37
+ """Specifies incorrect customizer."""
38
+
39
+
40
+ class GarfVirtualColumnError(GarfQueryError):
41
+ """Specifies incorrect virtual column type."""
42
+
43
+
44
+ class GarfFieldError(GarfQueryError):
45
+ """Specifies incorrect fields from API."""
46
+
47
+
48
+ class GarfMacroError(GarfQueryError):
49
+ """Specifies incorrect macro in Garf query."""
50
+
51
+
52
+ class GarfResourceError(GarfQueryError):
53
+ """Specifies incorrect resource name in the query."""
54
+
55
+
32
56
  @dataclasses.dataclass
33
57
  class ProcessedField:
34
58
  """Helper class to store fields with its customizers.
@@ -141,7 +165,7 @@ class VirtualColumn:
141
165
  substitute_expression=substitute_expression.replace('.', '_'),
142
166
  )
143
167
  if not _is_quoted_string(field):
144
- raise exceptions.GarfFieldException(f"Incorrect field '{field}'.")
168
+ raise GarfFieldError(f"Incorrect field '{field}'.")
145
169
  field = field.replace("'", '').replace('"', '')
146
170
  field = field.format(**macros) if macros else field
147
171
  return VirtualColumn(type='built-in', value=field)
@@ -183,9 +207,7 @@ class ExtractedLineElements:
183
207
  else:
184
208
  customizer = {}
185
209
  if virtual_column and not alias:
186
- raise exceptions.GarfVirtualColumnException(
187
- 'Virtual attributes should be aliased'
188
- )
210
+ raise GarfVirtualColumnError('Virtual attributes should be aliased')
189
211
  return ExtractedLineElements(
190
212
  field=_format_type_field_name(field)
191
213
  if not virtual_column and field
@@ -381,9 +403,7 @@ class QuerySpecification(CommonParametersMixin, TemplateProcessorMixin):
381
403
  try:
382
404
  self.query.text = query_text.format(**self.macros).strip()
383
405
  except KeyError as e:
384
- raise exceptions.GarfMacroException(
385
- f'No value provided for macro {str(e)}.'
386
- ) from e
406
+ raise GarfMacroError(f'No value provided for macro {str(e)}.') from e
387
407
  return self
388
408
 
389
409
  def remove_comments(self) -> Self:
@@ -424,9 +444,7 @@ class QuerySpecification(CommonParametersMixin, TemplateProcessorMixin):
424
444
  ):
425
445
  self.query.resource_name = str(resource_name[0]).strip()
426
446
  return self
427
- raise exceptions.GarfResourceException(
428
- f'No resource found in query: {self.query.text}'
429
- )
447
+ raise GarfResourceError(f'No resource found in query: {self.query.text}')
430
448
 
431
449
  def extract_fields(self) -> Self:
432
450
  for line in self._extract_query_lines():
garf_core/report.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Google LLC
1
+ # Copyright 2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Google LLf
1
+ # Copyright 2025 Google LLf
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -126,12 +126,17 @@ class RestApiReportFetcher(ApiReportFetcher):
126
126
  self,
127
127
  endpoint: str,
128
128
  parser: parsers.BaseParser = parsers.DictParser,
129
+ query_specification_builder: query_editor.QuerySpecification = (
130
+ query_editor.QuerySpecification
131
+ ),
132
+ **kwargs: str,
129
133
  ) -> None:
130
134
  """Instantiates RestApiReportFetcher.
131
135
 
132
136
  Args:
133
137
  endpoint: URL of API endpoint.
134
138
  parser: Type of parser to convert API response.
139
+ query_specification_builder: Class to perform query parsing.
135
140
  """
136
- self.api_client = api_clients.RestApiClient(endpoint)
137
- self.parser = parser()
141
+ api_client = api_clients.RestApiClient(endpoint)
142
+ super().__init__(api_client, parser, query_specification_builder, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: garf-core
3
- Version: 0.0.8
3
+ Version: 0.0.9
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>
6
6
  License: Apache 2.0
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
16
  Classifier: Operating System :: OS Independent
@@ -19,6 +20,8 @@ Description-Content-Type: text/markdown
19
20
  Requires-Dist: python-dateutil
20
21
  Requires-Dist: jinja2==3.1.4
21
22
  Requires-Dist: typing-extensions
23
+ Requires-Dist: requests
24
+ Requires-Dist: pyyaml
22
25
  Provides-Extra: pandas
23
26
  Requires-Dist: pandas; extra == "pandas"
24
27
  Provides-Extra: polars
@@ -0,0 +1,13 @@
1
+ garf_core/__init__.py,sha256=yBtrakQ_ly__BHdpAtsmIizhSJvauvxZNhDIafLx3b8,598
2
+ garf_core/api_clients.py,sha256=CIwiBHTwPb5aaGdcy4shkREEMxKTkWbYdpzXddNs6MI,2235
3
+ garf_core/base_query.py,sha256=ZDAw2ojmismXRO0HXEvKDukpS7OAc7390LnM8kvCSCY,1201
4
+ garf_core/exceptions.py,sha256=7HvJPFSUKQCfhlAZ8KYxZKi6Zf-Ifa73PPz48iFFWHk,1248
5
+ garf_core/parsers.py,sha256=Uj7aT7roYUofivrBKVKWfcSeG37oYGGLIJ-op2C3hZc,3238
6
+ garf_core/query_editor.py,sha256=uOai_N5MeU0hEv8fVSMDsH-sw57rpHiHi83xM1nAG9M,16432
7
+ garf_core/report.py,sha256=4ZsqeyNyZFu46CQQJEFDL7y-_8i1tqqyjljv6cOMXPI,17852
8
+ garf_core/report_fetcher.py,sha256=1PtJ8iQwlEcUeW6k1lVZiXh-3hop5x2YHQe-EaKYGNs,4405
9
+ garf_core-0.0.9.dist-info/METADATA,sha256=d19Mcu0BeoKVyAxQzNArvmqdSgBWZaQjEImi7T3vEpM,2431
10
+ garf_core-0.0.9.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
11
+ garf_core-0.0.9.dist-info/entry_points.txt,sha256=ODxSZXwWEIXQAjRwBQsBJ6GYw8oPK6DYTo0oDVkKCpo,39
12
+ garf_core-0.0.9.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
13
+ garf_core-0.0.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- garf_core/__init__.py,sha256=-2NI7Qm5ndrlcaz3itBBuFXOKu4anul2TdJro2G5oRo,598
2
- garf_core/api_clients.py,sha256=FKmwts_QBV_E4aTmrnsZtow0CmSExLibfeSkMSrudU0,2235
3
- garf_core/base_query.py,sha256=uEhKP56wcFtMiSwYOxoZ0q7OEvURGVOeRdpSYjxWLho,1201
4
- garf_core/exceptions.py,sha256=4qvNN-8GqbbVsrLKxJwBeX1FUtWpKbhUWyvm7anD3iE,1916
5
- garf_core/parsers.py,sha256=Y4wwpDXRZky7LBTBklvzk-aDNxgRL5RJg52rSdH5t9Q,2928
6
- garf_core/query_editor.py,sha256=EzwjFnbje-RcP9VwWQwzCpy8lipt4tph3Kv6q5Lly5o,15994
7
- garf_core/report.py,sha256=xlkZ44cdT_uGENuNeGwy69h7XLtwjdUjyPw3S1m-zc8,17852
8
- garf_core/report_fetcher.py,sha256=j1ewWoUvnEGM5vmWihFJ7vSVVEG1t2yzapKhylc-7eQ,4157
9
- garf_core-0.0.8.dist-info/METADATA,sha256=Ap-TAWp1wzDtXO6WPUBE1ajGnzQ_YMUIRiIhZbzLcm8,2334
10
- garf_core-0.0.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
- garf_core-0.0.8.dist-info/entry_points.txt,sha256=ODxSZXwWEIXQAjRwBQsBJ6GYw8oPK6DYTo0oDVkKCpo,39
12
- garf_core-0.0.8.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
13
- garf_core-0.0.8.dist-info/RECORD,,