garf-core 0.0.7__tar.gz → 0.0.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: garf-core
3
- Version: 0.0.7
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
@@ -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.7'
15
+ __version__ = '0.0.9'
@@ -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 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.
@@ -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."""
@@ -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):
@@ -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():
@@ -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.
@@ -38,12 +38,17 @@ class ApiReportFetcher:
38
38
 
39
39
  Attributes:
40
40
  api_client: a client used for connecting to API.
41
+ parser: Type of parser to convert API response.
42
+ query_specification_builder: Class to perform query parsing.
41
43
  """
42
44
 
43
45
  def __init__(
44
46
  self,
45
47
  api_client: api_clients.BaseApiClient,
46
48
  parser: parsers.BaseParser = parsers.ListParser,
49
+ query_specification_builder: query_editor.QuerySpecification = (
50
+ query_editor.QuerySpecification
51
+ ),
47
52
  **kwargs: str,
48
53
  ) -> None:
49
54
  """Instantiates ApiReportFetcher based on provided api client.
@@ -51,9 +56,11 @@ class ApiReportFetcher:
51
56
  Args:
52
57
  api_client: Instantiated api client.
53
58
  parser: Type of parser to convert API response.
59
+ query_specification_builder: Class to perform query parsing.
54
60
  """
55
61
  self.api_client = api_client
56
62
  self.parser = parser()
63
+ self.query_specification_builder = query_specification_builder
57
64
  self.query_args = kwargs
58
65
 
59
66
  async def afetch(
@@ -65,12 +72,12 @@ class ApiReportFetcher:
65
72
  """Asynchronously fetches data from API based on query_specification.
66
73
 
67
74
  Args:
68
- query_specification: Query text that will be passed to API
69
- alongside column_names, customizers and virtual columns.
70
- args: Arguments that need to be passed to the query.
75
+ query_specification: Query text that will be passed to API
76
+ alongside column_names, customizers and virtual columns.
77
+ args: Arguments that need to be passed to the query.
71
78
 
72
79
  Returns:
73
- GarfReport with results of query execution.
80
+ GarfReport with results of query execution.
74
81
  """
75
82
  return self.fetch(query_specification, args, **kwargs)
76
83
 
@@ -83,19 +90,19 @@ class ApiReportFetcher:
83
90
  """Fetches data from API based on query_specification.
84
91
 
85
92
  Args:
86
- query_specification: Query text that will be passed to API
87
- alongside column_names, customizers and virtual columns.
88
- args: Arguments that need to be passed to the query.
93
+ query_specification: Query text that will be passed to API
94
+ alongside column_names, customizers and virtual columns.
95
+ args: Arguments that need to be passed to the query.
89
96
 
90
97
  Returns:
91
- GarfReport with results of query execution.
98
+ GarfReport with results of query execution.
92
99
 
93
100
  Raises:
94
- GarfExecutorException:
95
- When customer_ids are not provided or API returned error.
101
+ GarfExecutorException:
102
+ When customer_ids are not provided or API returned error.
96
103
  """
97
104
  if not isinstance(query_specification, query_editor.QuerySpecification):
98
- query_specification = query_editor.QuerySpecification(
105
+ query_specification = self.query_specification_builder(
99
106
  text=str(query_specification),
100
107
  args=args,
101
108
  )
@@ -119,12 +126,17 @@ class RestApiReportFetcher(ApiReportFetcher):
119
126
  self,
120
127
  endpoint: str,
121
128
  parser: parsers.BaseParser = parsers.DictParser,
129
+ query_specification_builder: query_editor.QuerySpecification = (
130
+ query_editor.QuerySpecification
131
+ ),
132
+ **kwargs: str,
122
133
  ) -> None:
123
134
  """Instantiates RestApiReportFetcher.
124
135
 
125
136
  Args:
126
137
  endpoint: URL of API endpoint.
127
138
  parser: Type of parser to convert API response.
139
+ query_specification_builder: Class to perform query parsing.
128
140
  """
129
- self.api_client = api_clients.RestApiClient(endpoint)
130
- 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.7
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
@@ -1,6 +1,8 @@
1
1
  python-dateutil
2
2
  jinja2==3.1.4
3
3
  typing-extensions
4
+ requests
5
+ pyyaml
4
6
 
5
7
  [all]
6
8
  garf-core[pandas,polars]
@@ -8,6 +8,8 @@ dependencies = [
8
8
  "python-dateutil",
9
9
  "jinja2==3.1.4",
10
10
  "typing-extensions",
11
+ "requests",
12
+ "pyyaml",
11
13
  ]
12
14
  authors = [
13
15
  {name = "Google Inc. (gTech gPS CSE team)", email = "no-reply@google.com"},
@@ -23,6 +25,7 @@ classifiers = [
23
25
  "Programming Language :: Python :: 3.10",
24
26
  "Programming Language :: Python :: 3.11",
25
27
  "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
26
29
  "Intended Audience :: Developers",
27
30
  "Topic :: Software Development :: Libraries :: Python Modules",
28
31
  "Operating System :: OS Independent",
File without changes
File without changes