garf-core 0.1.4.post1__py3-none-any.whl → 0.1.5.post0__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 +15 -1
- garf_core/api_clients.py +11 -15
- garf_core/parsers.py +6 -8
- garf_core/query_editor.py +3 -1
- garf_core/report.py +16 -16
- {garf_core-0.1.4.post1.dist-info → garf_core-0.1.5.post0.dist-info}/METADATA +1 -1
- garf_core-0.1.5.post0.dist-info/RECORD +16 -0
- garf_core-0.1.4.post1.dist-info/RECORD +0 -16
- {garf_core-0.1.4.post1.dist-info → garf_core-0.1.5.post0.dist-info}/WHEEL +0 -0
- {garf_core-0.1.4.post1.dist-info → garf_core-0.1.5.post0.dist-info}/entry_points.txt +0 -0
- {garf_core-0.1.4.post1.dist-info → garf_core-0.1.5.post0.dist-info}/top_level.txt +0 -0
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
|
-
|
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.post0'
|
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:
|
51
|
+
self, request: query_editor.BaseQueryElements, **kwargs: str
|
56
52
|
) -> GarfApiResponse:
|
57
53
|
"""Method for getting response."""
|
58
54
|
|
@@ -69,11 +65,11 @@ class RestApiClient(BaseClient):
|
|
69
65
|
|
70
66
|
@override
|
71
67
|
def get_response(
|
72
|
-
self, request:
|
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:
|
76
|
-
return GarfApiResponse(response.json())
|
72
|
+
return GarfApiResponse(results=response.json())
|
77
73
|
raise GarfApiError('Failed to get data from API')
|
78
74
|
|
79
75
|
|
@@ -87,7 +83,7 @@ class FakeApiClient(BaseClient):
|
|
87
83
|
|
88
84
|
@override
|
89
85
|
def get_response(
|
90
|
-
self, request:
|
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
|
23
|
+
from typing import Any
|
24
24
|
|
25
|
-
from typing_extensions import
|
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(
|
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
|
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[
|
48
|
+
results: Sequence[Sequence[api_clients.ApiRowElement]] | None = None,
|
49
49
|
column_names: Sequence[str] | None = None,
|
50
|
-
results_placeholder: Sequence[Sequence[
|
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[
|
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,
|
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
|
-
) ->
|
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 |
|
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(
|
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:
|
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,
|
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) ->
|
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) ->
|
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) ->
|
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:
|
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) ->
|
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) ->
|
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.
|
3
|
+
Version: 0.1.5.post0
|
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=ZKDzKwZl1HtqUnBH6ijUMZgu6dRZJsbgMGuL8s9gzNU,960
|
2
|
+
garf_core/api_clients.py,sha256=P9fSy0xUXqmQoupWGWP1-SVBz_yZ80Jh58fvkh2dY8s,4694
|
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.post0.dist-info/METADATA,sha256=-YyPa7wDx_3dzKaWEPlEupNyg2QtKblNLvem6pWhew0,2433
|
13
|
+
garf_core-0.1.5.post0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
garf_core-0.1.5.post0.dist-info/entry_points.txt,sha256=u4h-ujHO1hbxVXRQzwcC4ftju9_KBYtq5mCLKEBHMj0,69
|
15
|
+
garf_core-0.1.5.post0.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
|
16
|
+
garf_core-0.1.5.post0.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,,
|
File without changes
|
File without changes
|
File without changes
|