garf-core 0.0.12__tar.gz → 0.1.0__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.
- {garf_core-0.0.12 → garf_core-0.1.0}/PKG-INFO +6 -8
- {garf_core-0.0.12 → garf_core-0.1.0}/README.md +5 -7
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/__init__.py +1 -1
- garf_core-0.1.0/garf_core/api_clients.py +172 -0
- garf_core-0.1.0/garf_core/fetchers/__init__.py +23 -0
- garf_core-0.1.0/garf_core/fetchers/fake.py +74 -0
- garf_core-0.1.0/garf_core/fetchers/rest.py +58 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/report_fetcher.py +8 -32
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core.egg-info/PKG-INFO +6 -8
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core.egg-info/SOURCES.txt +4 -1
- garf_core-0.1.0/garf_core.egg-info/entry_points.txt +3 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/pyproject.toml +2 -1
- garf_core-0.0.12/garf_core/api_clients.py +0 -86
- garf_core-0.0.12/garf_core.egg-info/entry_points.txt +0 -2
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/base_query.py +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/exceptions.py +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/parsers.py +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/query_editor.py +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core/report.py +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core.egg-info/dependency_links.txt +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core.egg-info/requires.txt +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/garf_core.egg-info/top_level.txt +0 -0
- {garf_core-0.0.12 → garf_core-0.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: garf-core
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
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
|
@@ -40,15 +40,13 @@ Requires-Dist: garf-core[pandas,polars]; extra == "all"
|
|
40
40
|
|
41
41
|
These abstractions are designed to be as modular and simple as possible:
|
42
42
|
|
43
|
-
* `BaseApiClient` - an interface for connecting to APIs.
|
44
|
-
* `
|
43
|
+
* `BaseApiClient` - an interface for connecting to APIs. Check [default implementations](docs/builtin-functionality.md#apiclients)
|
44
|
+
* `BaseParser` - an interface to parse results from the API. Check [default implementations](docs/builtin-functionality.md#parsers)
|
45
|
+
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API. [Default implementations](docs/builtin-functionality.md#apireportfetchers)
|
46
|
+
|
45
47
|
* `QuerySpecification` - parsed SQL-query into various elements.
|
46
|
-
* `
|
47
|
-
* `ListParser` - returns results from API as a raw list.
|
48
|
-
* `DictParser` - returns results from API as a formatted dict.
|
49
|
-
* `NumericDictParser` - returns results from API as a formatted dict with converted numeric values.
|
48
|
+
* `BaseQuery` - protocol for all class based queries.
|
50
49
|
* `GarfReport` - contains data from API in a format that is easy to write and interact with.
|
51
|
-
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API.
|
52
50
|
|
53
51
|
## Installation
|
54
52
|
|
@@ -8,15 +8,13 @@
|
|
8
8
|
|
9
9
|
These abstractions are designed to be as modular and simple as possible:
|
10
10
|
|
11
|
-
* `BaseApiClient` - an interface for connecting to APIs.
|
12
|
-
* `
|
11
|
+
* `BaseApiClient` - an interface for connecting to APIs. Check [default implementations](docs/builtin-functionality.md#apiclients)
|
12
|
+
* `BaseParser` - an interface to parse results from the API. Check [default implementations](docs/builtin-functionality.md#parsers)
|
13
|
+
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API. [Default implementations](docs/builtin-functionality.md#apireportfetchers)
|
14
|
+
|
13
15
|
* `QuerySpecification` - parsed SQL-query into various elements.
|
14
|
-
* `
|
15
|
-
* `ListParser` - returns results from API as a raw list.
|
16
|
-
* `DictParser` - returns results from API as a formatted dict.
|
17
|
-
* `NumericDictParser` - returns results from API as a formatted dict with converted numeric values.
|
16
|
+
* `BaseQuery` - protocol for all class based queries.
|
18
17
|
* `GarfReport` - contains data from API in a format that is easy to write and interact with.
|
19
|
-
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API.
|
20
18
|
|
21
19
|
## Installation
|
22
20
|
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
"""Module for defining client to interact with API."""
|
15
|
+
|
16
|
+
from __future__ import annotations
|
17
|
+
|
18
|
+
import abc
|
19
|
+
import contextlib
|
20
|
+
import csv
|
21
|
+
import dataclasses
|
22
|
+
import json
|
23
|
+
import os
|
24
|
+
import pathlib
|
25
|
+
from collections.abc import Sequence
|
26
|
+
from typing import Any
|
27
|
+
|
28
|
+
import requests
|
29
|
+
from typing_extensions import override
|
30
|
+
|
31
|
+
from garf_core import exceptions
|
32
|
+
|
33
|
+
|
34
|
+
@dataclasses.dataclass
|
35
|
+
class GarfApiRequest:
|
36
|
+
"""Base class for specifying request."""
|
37
|
+
|
38
|
+
|
39
|
+
@dataclasses.dataclass
|
40
|
+
class GarfApiResponse:
|
41
|
+
"""Base class for specifying response."""
|
42
|
+
|
43
|
+
results: list
|
44
|
+
|
45
|
+
|
46
|
+
class GarfApiError(exceptions.GarfError):
|
47
|
+
"""API specific exception."""
|
48
|
+
|
49
|
+
|
50
|
+
class BaseClient(abc.ABC):
|
51
|
+
"""Base API client class."""
|
52
|
+
|
53
|
+
@abc.abstractmethod
|
54
|
+
def get_response(
|
55
|
+
self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
|
56
|
+
) -> GarfApiResponse:
|
57
|
+
"""Method for getting response."""
|
58
|
+
|
59
|
+
|
60
|
+
class RestApiClient(BaseClient):
|
61
|
+
"""Specifies REST client."""
|
62
|
+
|
63
|
+
OK = 200
|
64
|
+
|
65
|
+
def __init__(self, endpoint: str, **kwargs: str) -> None:
|
66
|
+
"""Initializes RestApiClient."""
|
67
|
+
self.endpoint = endpoint
|
68
|
+
self.query_args = kwargs
|
69
|
+
|
70
|
+
@override
|
71
|
+
def get_response(
|
72
|
+
self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
|
73
|
+
) -> GarfApiResponse:
|
74
|
+
response = requests.get(f'{self.endpoint}/{request.resource_name}')
|
75
|
+
if response.status_code == self.OK:
|
76
|
+
return GarfApiResponse(response.json())
|
77
|
+
raise GarfApiError('Failed to get data from API')
|
78
|
+
|
79
|
+
|
80
|
+
class FakeApiClient(BaseClient):
|
81
|
+
"""Fake class for specifying API client."""
|
82
|
+
|
83
|
+
def __init__(self, results: Sequence[dict[str, Any]], **kwargs: str) -> None:
|
84
|
+
"""Initializes FakeApiClient."""
|
85
|
+
self.results = list(results)
|
86
|
+
self.kwargs = kwargs
|
87
|
+
|
88
|
+
@override
|
89
|
+
def get_response(
|
90
|
+
self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
|
91
|
+
) -> GarfApiResponse:
|
92
|
+
del request
|
93
|
+
return GarfApiResponse(results=self.results)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def from_file(cls, file_location: str | os.PathLike[str]) -> FakeApiClient:
|
97
|
+
"""Initializes FakeApiClient from json or csv files.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
file_location: Path of file with data.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
Initialized client.
|
104
|
+
|
105
|
+
Raises:
|
106
|
+
GarfApiError: When file with unsupported extension is provided.
|
107
|
+
"""
|
108
|
+
if str(file_location).endswith('.json'):
|
109
|
+
return FakeApiClient.from_json(file_location)
|
110
|
+
if str(file_location).endswith('.csv'):
|
111
|
+
return FakeApiClient.from_csv(file_location)
|
112
|
+
raise GarfApiError(
|
113
|
+
'Unsupported file extension, only csv and json are supported.'
|
114
|
+
)
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def from_json(cls, file_location: str | os.PathLike[str]) -> FakeApiClient:
|
118
|
+
"""Initializes FakeApiClient from json file.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
file_location: Path of file with data.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
Initialized client.
|
125
|
+
|
126
|
+
Raises:
|
127
|
+
GarfApiError: When file with data not found.
|
128
|
+
"""
|
129
|
+
try:
|
130
|
+
with pathlib.Path.open(file_location, 'r', encoding='utf-8') as f:
|
131
|
+
data = json.load(f)
|
132
|
+
return FakeApiClient(data)
|
133
|
+
except FileNotFoundError as e:
|
134
|
+
raise GarfApiError(f'Failed to open {file_location}') from e
|
135
|
+
|
136
|
+
@classmethod
|
137
|
+
def from_csv(cls, file_location: str | os.PathLike[str]) -> FakeApiClient:
|
138
|
+
"""Initializes FakeApiClient from csv file.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
file_location: Path of file with data.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
Initialized client.
|
145
|
+
|
146
|
+
Raises:
|
147
|
+
GarfApiError: When file with data not found.
|
148
|
+
"""
|
149
|
+
try:
|
150
|
+
with pathlib.Path.open(file_location, 'r', encoding='utf-8') as f:
|
151
|
+
reader = csv.DictReader(f)
|
152
|
+
data = []
|
153
|
+
for row in reader:
|
154
|
+
data.append(
|
155
|
+
{key: _field_converter(value) for key, value in row.items()}
|
156
|
+
)
|
157
|
+
return FakeApiClient(data)
|
158
|
+
except FileNotFoundError as e:
|
159
|
+
raise GarfApiError(f'Failed to open {file_location}') from e
|
160
|
+
|
161
|
+
|
162
|
+
def _field_converter(field: str):
|
163
|
+
if isinstance(field, str) and (lower_field := field.lower()) in (
|
164
|
+
'true',
|
165
|
+
'false',
|
166
|
+
):
|
167
|
+
return lower_field == 'true'
|
168
|
+
with contextlib.suppress(ValueError):
|
169
|
+
return int(field)
|
170
|
+
with contextlib.suppress(ValueError):
|
171
|
+
return float(field)
|
172
|
+
return field
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""Built-in fetchers."""
|
16
|
+
|
17
|
+
from garf_core.fetchers.fake import FakeApiReportFetcher
|
18
|
+
from garf_core.fetchers.rest import RestApiReportFetcher
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
'FakeApiReportFetcher',
|
22
|
+
'RestApiReportFetcher',
|
23
|
+
]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright 2025 Google LLf
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
16
|
+
|
17
|
+
"""Getting fake data from memory or a file."""
|
18
|
+
|
19
|
+
from __future__ import annotations
|
20
|
+
|
21
|
+
import logging
|
22
|
+
import os
|
23
|
+
|
24
|
+
from garf_core import (
|
25
|
+
api_clients,
|
26
|
+
parsers,
|
27
|
+
query_editor,
|
28
|
+
report_fetcher,
|
29
|
+
)
|
30
|
+
|
31
|
+
logger = logging.getLogger(__name__)
|
32
|
+
|
33
|
+
|
34
|
+
class FakeApiReportFetcher(report_fetcher.ApiReportFetcher):
|
35
|
+
"""Returns simulated data."""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
data: list[dict[str, parsers.ApiRowElement]] | None = None,
|
40
|
+
parser: parsers.BaseParser = parsers.DictParser,
|
41
|
+
query_specification_builder: query_editor.QuerySpecification = (
|
42
|
+
query_editor.QuerySpecification
|
43
|
+
),
|
44
|
+
data_location: str | os.PathLike[str] | None = None,
|
45
|
+
csv_location: str | os.PathLike[str] | None = None,
|
46
|
+
json_location: str | os.PathLike[str] | None = None,
|
47
|
+
**kwargs: str,
|
48
|
+
) -> None:
|
49
|
+
if not data and not (
|
50
|
+
data_location := json_location or csv_location or data_location
|
51
|
+
):
|
52
|
+
raise report_fetcher.ApiReportFetcherError(
|
53
|
+
'Missing fake data for the fetcher.'
|
54
|
+
)
|
55
|
+
api_client = (
|
56
|
+
api_clients.FakeApiClient(data)
|
57
|
+
if data
|
58
|
+
else api_clients.FakeApiClient.from_file(data_location)
|
59
|
+
)
|
60
|
+
super().__init__(api_client, parser, query_specification_builder, **kwargs)
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def from_csv(
|
64
|
+
cls, file_location: str | os.PathLike[str]
|
65
|
+
) -> FakeApiReportFetcher:
|
66
|
+
"""Initialized FakeApiReportFetcher from a csv file."""
|
67
|
+
return FakeApiReportFetcher(csv_location=file_location)
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def from_json(
|
71
|
+
cls, file_location: str | os.PathLike[str]
|
72
|
+
) -> FakeApiReportFetcher:
|
73
|
+
"""Initialized FakeApiReportFetcher from a json file."""
|
74
|
+
return FakeApiReportFetcher(json_location=file_location)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2025 Google LLf
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
16
|
+
|
17
|
+
"""Module for getting data from Rest APIs based on a query."""
|
18
|
+
|
19
|
+
from __future__ import annotations
|
20
|
+
|
21
|
+
import logging
|
22
|
+
|
23
|
+
from garf_core import (
|
24
|
+
api_clients,
|
25
|
+
parsers,
|
26
|
+
query_editor,
|
27
|
+
report_fetcher,
|
28
|
+
)
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
class RestApiReportFetcher(report_fetcher.ApiReportFetcher):
|
34
|
+
"""Fetches data from an REST API endpoint.
|
35
|
+
|
36
|
+
Attributes:
|
37
|
+
api_client: Initialized RestApiClient.
|
38
|
+
parser: Type of parser to convert API response.
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(
|
42
|
+
self,
|
43
|
+
endpoint: str,
|
44
|
+
parser: parsers.BaseParser = parsers.DictParser,
|
45
|
+
query_specification_builder: query_editor.QuerySpecification = (
|
46
|
+
query_editor.QuerySpecification
|
47
|
+
),
|
48
|
+
**kwargs: str,
|
49
|
+
) -> None:
|
50
|
+
"""Instantiates RestApiReportFetcher.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
endpoint: URL of API endpoint.
|
54
|
+
parser: Type of parser to convert API response.
|
55
|
+
query_specification_builder: Class to perform query parsing.
|
56
|
+
"""
|
57
|
+
api_client = api_clients.RestApiClient(endpoint)
|
58
|
+
super().__init__(api_client, parser, query_specification_builder, **kwargs)
|
@@ -16,17 +16,17 @@
|
|
16
16
|
|
17
17
|
"""Module for getting data from API based on a query.
|
18
18
|
|
19
|
-
ApiReportFetcher
|
20
|
-
and returning GarfReport.
|
19
|
+
ApiReportFetcher fetches data from API, parses it and returns GarfReport.
|
21
20
|
"""
|
22
21
|
|
23
22
|
from __future__ import annotations
|
24
23
|
|
25
24
|
import logging
|
26
|
-
from typing import
|
25
|
+
from typing import Callable
|
27
26
|
|
28
27
|
from garf_core import (
|
29
28
|
api_clients,
|
29
|
+
exceptions,
|
30
30
|
parsers,
|
31
31
|
query_editor,
|
32
32
|
report,
|
@@ -35,6 +35,10 @@ from garf_core import (
|
|
35
35
|
logger = logging.getLogger(__name__)
|
36
36
|
|
37
37
|
|
38
|
+
class ApiReportFetcherError(exceptions.GarfError):
|
39
|
+
"""Base exception for all ApiReportFetchers."""
|
40
|
+
|
41
|
+
|
38
42
|
class ApiReportFetcher:
|
39
43
|
"""Class responsible for getting data from report API.
|
40
44
|
|
@@ -47,7 +51,7 @@ class ApiReportFetcher:
|
|
47
51
|
def __init__(
|
48
52
|
self,
|
49
53
|
api_client: api_clients.BaseApiClient,
|
50
|
-
parser: parsers.BaseParser = parsers.
|
54
|
+
parser: parsers.BaseParser = parsers.DictParser,
|
51
55
|
query_specification_builder: query_editor.QuerySpecification = (
|
52
56
|
query_editor.QuerySpecification
|
53
57
|
),
|
@@ -135,31 +139,3 @@ class ApiReportFetcher:
|
|
135
139
|
return report.GarfReport(
|
136
140
|
results=parsed_response, column_names=query.column_names
|
137
141
|
)
|
138
|
-
|
139
|
-
|
140
|
-
class RestApiReportFetcher(ApiReportFetcher):
|
141
|
-
"""Fetches data from an REST API endpoint.
|
142
|
-
|
143
|
-
Attributes:
|
144
|
-
api_client: Initialized RestApiClient.
|
145
|
-
parser: Type of parser to convert API response.
|
146
|
-
"""
|
147
|
-
|
148
|
-
def __init__(
|
149
|
-
self,
|
150
|
-
endpoint: str,
|
151
|
-
parser: parsers.BaseParser = parsers.DictParser,
|
152
|
-
query_specification_builder: query_editor.QuerySpecification = (
|
153
|
-
query_editor.QuerySpecification
|
154
|
-
),
|
155
|
-
**kwargs: str,
|
156
|
-
) -> None:
|
157
|
-
"""Instantiates RestApiReportFetcher.
|
158
|
-
|
159
|
-
Args:
|
160
|
-
endpoint: URL of API endpoint.
|
161
|
-
parser: Type of parser to convert API response.
|
162
|
-
query_specification_builder: Class to perform query parsing.
|
163
|
-
"""
|
164
|
-
api_client = api_clients.RestApiClient(endpoint)
|
165
|
-
super().__init__(api_client, parser, query_specification_builder, **kwargs)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: garf-core
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
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
|
@@ -40,15 +40,13 @@ Requires-Dist: garf-core[pandas,polars]; extra == "all"
|
|
40
40
|
|
41
41
|
These abstractions are designed to be as modular and simple as possible:
|
42
42
|
|
43
|
-
* `BaseApiClient` - an interface for connecting to APIs.
|
44
|
-
* `
|
43
|
+
* `BaseApiClient` - an interface for connecting to APIs. Check [default implementations](docs/builtin-functionality.md#apiclients)
|
44
|
+
* `BaseParser` - an interface to parse results from the API. Check [default implementations](docs/builtin-functionality.md#parsers)
|
45
|
+
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API. [Default implementations](docs/builtin-functionality.md#apireportfetchers)
|
46
|
+
|
45
47
|
* `QuerySpecification` - parsed SQL-query into various elements.
|
46
|
-
* `
|
47
|
-
* `ListParser` - returns results from API as a raw list.
|
48
|
-
* `DictParser` - returns results from API as a formatted dict.
|
49
|
-
* `NumericDictParser` - returns results from API as a formatted dict with converted numeric values.
|
48
|
+
* `BaseQuery` - protocol for all class based queries.
|
50
49
|
* `GarfReport` - contains data from API in a format that is easy to write and interact with.
|
51
|
-
* `ApiReportFetcher` - responsible for fetching and parsing data from reporting API.
|
52
50
|
|
53
51
|
## Installation
|
54
52
|
|
@@ -13,4 +13,7 @@ garf_core.egg-info/SOURCES.txt
|
|
13
13
|
garf_core.egg-info/dependency_links.txt
|
14
14
|
garf_core.egg-info/entry_points.txt
|
15
15
|
garf_core.egg-info/requires.txt
|
16
|
-
garf_core.egg-info/top_level.txt
|
16
|
+
garf_core.egg-info/top_level.txt
|
17
|
+
garf_core/fetchers/__init__.py
|
18
|
+
garf_core/fetchers/fake.py
|
19
|
+
garf_core/fetchers/rest.py
|
@@ -39,7 +39,8 @@ dynamic=["version"]
|
|
39
39
|
version = {attr = "garf_core.__version__"}
|
40
40
|
|
41
41
|
[project.entry-points.garf]
|
42
|
-
rest = "garf_core.
|
42
|
+
rest = "garf_core.fetchers.rest"
|
43
|
+
fake = "garf_core.fetchers.fake"
|
43
44
|
|
44
45
|
[project.optional-dependencies]
|
45
46
|
pandas=[
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# Copyright 2025 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Module for defining client to interact with API."""
|
15
|
-
|
16
|
-
from __future__ import annotations
|
17
|
-
|
18
|
-
import abc
|
19
|
-
import dataclasses
|
20
|
-
from collections.abc import Sequence
|
21
|
-
|
22
|
-
import requests
|
23
|
-
from typing_extensions import override
|
24
|
-
|
25
|
-
from garf_core import exceptions
|
26
|
-
|
27
|
-
|
28
|
-
@dataclasses.dataclass
|
29
|
-
class GarfApiRequest:
|
30
|
-
"""Base class for specifying request."""
|
31
|
-
|
32
|
-
|
33
|
-
@dataclasses.dataclass
|
34
|
-
class GarfApiResponse:
|
35
|
-
"""Base class for specifying response."""
|
36
|
-
|
37
|
-
results: list
|
38
|
-
|
39
|
-
|
40
|
-
class GarfApiError(exceptions.GarfError):
|
41
|
-
"""API specific exception."""
|
42
|
-
|
43
|
-
|
44
|
-
class BaseClient(abc.ABC):
|
45
|
-
"""Base API client class."""
|
46
|
-
|
47
|
-
@abc.abstractmethod
|
48
|
-
def get_response(
|
49
|
-
self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
|
50
|
-
) -> GarfApiResponse:
|
51
|
-
"""Method for getting response."""
|
52
|
-
|
53
|
-
|
54
|
-
class RestApiClient(BaseClient):
|
55
|
-
"""Specifies REST client."""
|
56
|
-
|
57
|
-
OK = 200
|
58
|
-
|
59
|
-
def __init__(self, endpoint: str, **kwargs: str) -> None:
|
60
|
-
"""Initializes RestApiClient."""
|
61
|
-
self.endpoint = endpoint
|
62
|
-
self.query_args = kwargs
|
63
|
-
|
64
|
-
@override
|
65
|
-
def get_response(
|
66
|
-
self, request: GarfApiRequest = GarfApiRequest(), **kwargs: str
|
67
|
-
) -> GarfApiResponse:
|
68
|
-
response = requests.get(f'{self.endpoint}/{request.resource_name}')
|
69
|
-
if response.status_code == self.OK:
|
70
|
-
return GarfApiResponse(response.json())
|
71
|
-
raise GarfApiError('Failed to get data from API')
|
72
|
-
|
73
|
-
|
74
|
-
class FakeApiClient(BaseClient):
|
75
|
-
"""Fake class for specifying API client."""
|
76
|
-
|
77
|
-
def __init__(self, results: Sequence) -> None:
|
78
|
-
"""Initializes FakeApiClient."""
|
79
|
-
self.results = list(results)
|
80
|
-
|
81
|
-
@override
|
82
|
-
def get_response(
|
83
|
-
self, request: GarfApiRequest = GarfApiRequest()
|
84
|
-
) -> GarfApiResponse:
|
85
|
-
del request
|
86
|
-
return GarfApiResponse(results=self.results)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|