garf-core 0.1.4.post1__py3-none-any.whl → 0.1.5__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
@@ -11,5 +11,19 @@
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
+ """`garf-core` contains the base abstractions for garf framework.
14
15
 
15
- __version__ = '0.1.4.post1'
16
+ These abstractions are used by an implementation for a concrete reporting API.
17
+ """
18
+
19
+ from garf_core.base_query import BaseQuery
20
+ from garf_core.report import GarfReport
21
+ from garf_core.report_fetcher import ApiReportFetcher
22
+
23
+ __all__ = [
24
+ 'BaseQuery',
25
+ 'GarfReport',
26
+ 'ApiReportFetcher',
27
+ ]
28
+
29
+ __version__ = '0.1.5'
garf_core/api_clients.py CHANGED
@@ -18,29 +18,25 @@ from __future__ import annotations
18
18
  import abc
19
19
  import contextlib
20
20
  import csv
21
- import dataclasses
22
21
  import json
23
22
  import os
24
23
  import pathlib
25
24
  from collections.abc import Sequence
26
- from typing import Any
25
+ from typing import Any, Union
27
26
 
27
+ import pydantic
28
28
  import requests
29
- from typing_extensions import override
29
+ from typing_extensions import TypeAlias, override
30
30
 
31
- from garf_core import exceptions
31
+ from garf_core import exceptions, query_editor
32
32
 
33
+ ApiRowElement: TypeAlias = Union[int, float, str, bool, list, dict, None]
33
34
 
34
- @dataclasses.dataclass
35
- class GarfApiRequest:
36
- """Base class for specifying request."""
37
35
 
38
-
39
- @dataclasses.dataclass
40
- class GarfApiResponse:
36
+ class GarfApiResponse(pydantic.BaseModel):
41
37
  """Base class for specifying response."""
42
38
 
43
- results: list
39
+ results: list[ApiRowElement]
44
40
 
45
41
 
46
42
  class GarfApiError(exceptions.GarfError):
@@ -52,7 +48,7 @@ class BaseClient(abc.ABC):
52
48
 
53
49
  @abc.abstractmethod
54
50
  def get_response(
55
- self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
51
+ self, request: query_editor.BaseQueryElements, **kwargs: str
56
52
  ) -> GarfApiResponse:
57
53
  """Method for getting response."""
58
54
 
@@ -69,7 +65,7 @@ class RestApiClient(BaseClient):
69
65
 
70
66
  @override
71
67
  def get_response(
72
- self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
68
+ self, request: query_editor.BaseQueryElements, **kwargs: str
73
69
  ) -> GarfApiResponse:
74
70
  response = requests.get(f'{self.endpoint}/{request.resource_name}')
75
71
  if response.status_code == self.OK:
@@ -87,7 +83,7 @@ class FakeApiClient(BaseClient):
87
83
 
88
84
  @override
89
85
  def get_response(
90
- self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
86
+ self, request: query_editor.BaseQueryElements, **kwargs: str
91
87
  ) -> GarfApiResponse:
92
88
  del request
93
89
  return GarfApiResponse(results=self.results)
garf_core/parsers.py CHANGED
@@ -20,14 +20,12 @@ import contextlib
20
20
  import functools
21
21
  import operator
22
22
  from collections.abc import Mapping, MutableSequence
23
- from typing import Any, Union
23
+ from typing import Any
24
24
 
25
- from typing_extensions import TypeAlias, override
25
+ from typing_extensions import override
26
26
 
27
27
  from garf_core import api_clients, exceptions, query_editor
28
28
 
29
- ApiRowElement: TypeAlias = Union[int, float, str, bool, list, None]
30
-
31
29
 
32
30
  class BaseParser(abc.ABC):
33
31
  """An interface for all parsers to implement."""
@@ -41,7 +39,7 @@ class BaseParser(abc.ABC):
41
39
  def parse_response(
42
40
  self,
43
41
  response: api_clients.GarfApiResponse,
44
- ) -> list[list[ApiRowElement]]:
42
+ ) -> list[list[api_clients.ApiRowElement]]:
45
43
  """Parses response."""
46
44
  if not response.results:
47
45
  return [[]]
@@ -62,7 +60,7 @@ class ListParser(BaseParser):
62
60
  def parse_row(
63
61
  self,
64
62
  row: list,
65
- ) -> list[list[ApiRowElement]]:
63
+ ) -> list[list[api_clients.ApiRowElement]]:
66
64
  return row
67
65
 
68
66
 
@@ -73,7 +71,7 @@ class DictParser(BaseParser):
73
71
  def parse_row(
74
72
  self,
75
73
  row: list,
76
- ) -> list[list[ApiRowElement]]:
74
+ ) -> list[list[api_clients.ApiRowElement]]:
77
75
  if not isinstance(row, Mapping):
78
76
  raise GarfParserError
79
77
  result = []
@@ -88,7 +86,7 @@ class DictParser(BaseParser):
88
86
  key = key.split('.')
89
87
  try:
90
88
  return functools.reduce(operator.getitem, key, dictionary)
91
- except KeyError:
89
+ except (TypeError, KeyError):
92
90
  return None
93
91
 
94
92
 
garf_core/query_editor.py CHANGED
@@ -445,7 +445,9 @@ class QuerySpecification(CommonParametersMixin, TemplateProcessorMixin):
445
445
  return self
446
446
 
447
447
  def remove_trailing_comma(self) -> Self:
448
- self.text = re.sub(r',\s+from', ' FROM', self.query.text, re.IGNORECASE)
448
+ self.text = re.sub(
449
+ r',\s+from', ' FROM', self.query.text, count=0, flags=re.IGNORECASE
450
+ )
449
451
  return self
450
452
 
451
453
  def extract_resource_name(self) -> Self:
garf_core/report.py CHANGED
@@ -29,7 +29,7 @@ from collections import defaultdict
29
29
  from collections.abc import MutableSequence, Sequence
30
30
  from typing import Generator, Literal, get_args
31
31
 
32
- from garf_core import exceptions, parsers, query_editor
32
+ from garf_core import api_clients, exceptions, query_editor
33
33
 
34
34
 
35
35
  class GarfReport:
@@ -45,9 +45,9 @@ class GarfReport:
45
45
 
46
46
  def __init__(
47
47
  self,
48
- results: Sequence[Sequence[parsers.ApiRowElement]] | None = None,
48
+ results: Sequence[Sequence[api_clients.ApiRowElement]] | None = None,
49
49
  column_names: Sequence[str] | None = None,
50
- results_placeholder: Sequence[Sequence[parsers.ApiRowElement]]
50
+ results_placeholder: Sequence[Sequence[api_clients.ApiRowElement]]
51
51
  | None = None,
52
52
  query_specification: query_editor.BaseQueryElements | None = None,
53
53
  auto_convert_to_scalars: bool = True,
@@ -92,7 +92,7 @@ class GarfReport:
92
92
  row_type: Literal['list', 'dict', 'scalar'] = 'list',
93
93
  flatten: bool = False,
94
94
  distinct: bool = False,
95
- ) -> list[parsers.ApiRowElement]:
95
+ ) -> list[api_clients.ApiRowElement]:
96
96
  """Converts report to a list.
97
97
 
98
98
  Args:
@@ -137,7 +137,7 @@ class GarfReport:
137
137
  key_column: str,
138
138
  value_column: str | None = None,
139
139
  value_column_output: Literal['scalar', 'list'] = 'list',
140
- ) -> dict[str, parsers.ApiRowElement | list[parsers.ApiRowElement]]:
140
+ ) -> dict[str, api_clients.ApiRowElement | list[api_clients.ApiRowElement]]:
141
141
  """Converts report to dictionary.
142
142
 
143
143
  Args:
@@ -232,7 +232,7 @@ class GarfReport:
232
232
 
233
233
  def get_value(
234
234
  self, column_index: int = 0, row_index: int = 0
235
- ) -> parsers.ApiRowElement:
235
+ ) -> api_clients.ApiRowElement:
236
236
  """Extracts data from report as a scalar.
237
237
 
238
238
  Raises:
@@ -310,7 +310,7 @@ class GarfReport:
310
310
 
311
311
  def _get_rows_slice(
312
312
  self, key: slice | int
313
- ) -> GarfReport | GarfRow | parsers.ApiRowElement:
313
+ ) -> GarfReport | GarfRow | api_clients.ApiRowElement:
314
314
  """Gets one or several rows from the report.
315
315
 
316
316
  Args:
@@ -476,7 +476,7 @@ class GarfReport:
476
476
  data = json.loads(json_str)
477
477
 
478
478
  def validate_value(value):
479
- if not isinstance(value, get_args(parsers.ApiRowElement)):
479
+ if not isinstance(value, get_args(api_clients.ApiRowElement)):
480
480
  raise TypeError(
481
481
  f'Unsupported type {type(value)} for value {value}. '
482
482
  'Expected types: int, float, str, bool, list, or None.'
@@ -520,7 +520,7 @@ class GarfRow:
520
520
  """
521
521
 
522
522
  def __init__(
523
- self, data: parsers.ApiRowElement, column_names: Sequence[str]
523
+ self, data: api_clients.ApiRowElement, column_names: Sequence[str]
524
524
  ) -> None:
525
525
  """Initializes new GarfRow.
526
526
 
@@ -530,11 +530,11 @@ class GarfRow:
530
530
  super().__setattr__('data', data)
531
531
  super().__setattr__('column_names', column_names)
532
532
 
533
- def to_dict(self) -> dict[str, parsers.ApiRowElement]:
533
+ def to_dict(self) -> dict[str, api_clients.ApiRowElement]:
534
534
  """Maps column names to corresponding data point."""
535
535
  return {x[1]: x[0] for x in zip(self.data, self.column_names)}
536
536
 
537
- def get_value(self, column_index: int = 0) -> parsers.ApiRowElement:
537
+ def get_value(self, column_index: int = 0) -> api_clients.ApiRowElement:
538
538
  """Extracts data from row as a scalar.
539
539
 
540
540
  Raises:
@@ -548,7 +548,7 @@ class GarfRow:
548
548
  )
549
549
  return self.data[column_index]
550
550
 
551
- def __getattr__(self, element: str) -> parsers.ApiRowElement:
551
+ def __getattr__(self, element: str) -> api_clients.ApiRowElement:
552
552
  """Gets element from row as an attribute.
553
553
 
554
554
  Args:
@@ -564,7 +564,7 @@ class GarfRow:
564
564
  return self.data[self.column_names.index(element)]
565
565
  raise AttributeError(f'cannot find {element} element!')
566
566
 
567
- def __getitem__(self, element: str | int) -> parsers.ApiRowElement:
567
+ def __getitem__(self, element: str | int) -> api_clients.ApiRowElement:
568
568
  """Gets element from row by index.
569
569
 
570
570
  Args:
@@ -584,7 +584,7 @@ class GarfRow:
584
584
  return self.__getattr__(element)
585
585
  raise GarfReportError(f'cannot find {element} element!')
586
586
 
587
- def __setattr__(self, name: str, value: parsers.ApiRowElement) -> None:
587
+ def __setattr__(self, name: str, value: api_clients.ApiRowElement) -> None:
588
588
  """Sets new value for an attribute.
589
589
 
590
590
  Args:
@@ -609,7 +609,7 @@ class GarfRow:
609
609
  self.data.append(value)
610
610
  self.column_names.append(name)
611
611
 
612
- def get(self, item: str) -> parsers.ApiRowElement:
612
+ def get(self, item: str) -> api_clients.ApiRowElement:
613
613
  """Extracts value as dictionary get operation.
614
614
 
615
615
  Args:
@@ -620,7 +620,7 @@ class GarfRow:
620
620
  """
621
621
  return self.__getattr__(item)
622
622
 
623
- def __iter__(self) -> parsers.ApiRowElement:
623
+ def __iter__(self) -> api_clients.ApiRowElement:
624
624
  """Yields each element of a row."""
625
625
  for field in self.data:
626
626
  yield field
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: garf-core
3
- Version: 0.1.4.post1
3
+ Version: 0.1.5
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
@@ -0,0 +1,16 @@
1
+ garf_core/__init__.py,sha256=ZBeZWVi-UMFDXfbBNGJlAqiTVRvN923rQK8iEbu0VNQ,954
2
+ garf_core/api_clients.py,sha256=GQ1Iq3nhE8PiIlrCG1hDoXDUTF3udgcQnK40esnwCDY,4686
3
+ garf_core/base_query.py,sha256=ZDAw2ojmismXRO0HXEvKDukpS7OAc7390LnM8kvCSCY,1201
4
+ garf_core/exceptions.py,sha256=Gzvkl2M-rA_XQRAMd3CC62KHeFQE_b6uby0fD0pouw4,1269
5
+ garf_core/parsers.py,sha256=A4Yb614ydN_psKvyUfWZjnTG5-aooT0pz2Cm6CHUnC4,3283
6
+ garf_core/query_editor.py,sha256=QPbUz8jHloTcGm_Nz2Nog_sdYIPiDshtm_t45-R76pk,17147
7
+ garf_core/report.py,sha256=J2-oNV3Nh7ToYhm2fsdovkiE__-UhT0K5-wP0jBD0oQ,20330
8
+ garf_core/report_fetcher.py,sha256=dgErrkRv1bdPN0npX3atZbJrOpzy9OmoPC38e2FpBSQ,4883
9
+ garf_core/fetchers/__init__.py,sha256=_cSjg1D5RhUKxaVeVbaDdb8AAoI9glKJXgN5H4qXFkw,783
10
+ garf_core/fetchers/fake.py,sha256=fgJjxuHyd6EIUflUtj8r_HfaMS1YTDrOqDlaj6Kvbjs,2584
11
+ garf_core/fetchers/rest.py,sha256=-5B2-Ck_t3hG99ym59AKwlzctiDxRFI2Nnc8STBxRDo,2201
12
+ garf_core-0.1.5.dist-info/METADATA,sha256=o37Q9VN6ZQPMVuZiqs3MvRtVMzWM7Yl7Jp6fYTawIXs,2427
13
+ garf_core-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ garf_core-0.1.5.dist-info/entry_points.txt,sha256=u4h-ujHO1hbxVXRQzwcC4ftju9_KBYtq5mCLKEBHMj0,69
15
+ garf_core-0.1.5.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
16
+ garf_core-0.1.5.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- garf_core/__init__.py,sha256=wx96KFgXZaBUmTV19ZxJvuyZKiS2Xm_ehzAyXl6Cz84,604
2
- garf_core/api_clients.py,sha256=HGnRI-wK2TX79xDYQkE1CtVT4R4tZ7Ylq7dhcW5cNCQ,4669
3
- garf_core/base_query.py,sha256=ZDAw2ojmismXRO0HXEvKDukpS7OAc7390LnM8kvCSCY,1201
4
- garf_core/exceptions.py,sha256=Gzvkl2M-rA_XQRAMd3CC62KHeFQE_b6uby0fD0pouw4,1269
5
- garf_core/parsers.py,sha256=PmF8Ez0s1k2duSbO-0MZClslILxzbZyGMbTlhFjOtVE,3321
6
- garf_core/query_editor.py,sha256=yWKElbOFE2K_4vakE1izzpZGCwf6giff-j29KJohv_I,17120
7
- garf_core/report.py,sha256=O0SPxgVQckxi_QuD3vy1h_1mi5aMjQNw0LYP-jgN2Do,20262
8
- garf_core/report_fetcher.py,sha256=dgErrkRv1bdPN0npX3atZbJrOpzy9OmoPC38e2FpBSQ,4883
9
- garf_core/fetchers/__init__.py,sha256=_cSjg1D5RhUKxaVeVbaDdb8AAoI9glKJXgN5H4qXFkw,783
10
- garf_core/fetchers/fake.py,sha256=fgJjxuHyd6EIUflUtj8r_HfaMS1YTDrOqDlaj6Kvbjs,2584
11
- garf_core/fetchers/rest.py,sha256=-5B2-Ck_t3hG99ym59AKwlzctiDxRFI2Nnc8STBxRDo,2201
12
- garf_core-0.1.4.post1.dist-info/METADATA,sha256=3dKfHXvn9Yh28oFRRjfzZdJlUCZvAvNWecA-WNlVLys,2433
13
- garf_core-0.1.4.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- garf_core-0.1.4.post1.dist-info/entry_points.txt,sha256=u4h-ujHO1hbxVXRQzwcC4ftju9_KBYtq5mCLKEBHMj0,69
15
- garf_core-0.1.4.post1.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
16
- garf_core-0.1.4.post1.dist-info/RECORD,,