Skip to content

Commit e15f11d

Browse files
docs: add customization and usage sections
1 parent a219b63 commit e15f11d

File tree

11 files changed

+352
-16
lines changed

11 files changed

+352
-16
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Create API client for garf
2+
3+
ApiClient is responsible for sending request to an API based on the query.
4+
5+
## Define ApiClient class
6+
7+
Creating an API client is the easiest way of developing with `garf`.
8+
9+
10+
`garf-core` library has a `BaseClient` which one mandatory method you need to implement `get_response`.
11+
12+
Your implementation should take an instance of `garf_core.query_editor.BaseQueryElements` class and return `garf_core.api_clients.GarfApiResponse`.
13+
14+
* `BaseQueryElements` contains various elements parsed from the query such as fields, sorts, filters, and resource to get data from.
15+
* `GarfApiReponse` contains `results` property that should be a list of dictionary-like objects.
16+
17+
18+
Let see and example implementation of `MyApiClient`.
19+
20+
```python
21+
from garf_core import api_clients, query_editor
22+
23+
class MyApiClient(api_clients.BaseClient):
24+
25+
def get_response(
26+
request: query_editor.BaseQueryElements,
27+
**kwargs: str
28+
) -> api_clients.GarfApiResponse:
29+
results = ... # get results from your API somehow
30+
return api_clients.GarfApiResponse(results=results)
31+
```
32+
33+
## Use with ApiReportFetcher
34+
35+
Once your ApiClient class is defined, you can use with built-in `ApiReportFetcher`.
36+
37+
```python
38+
from garf_core import ApiReportFetcher
39+
40+
api_client = MyClient()
41+
report_fetcher = ApiReportFetcher(api_client=api_client)
42+
```
43+
!!! note
44+
[Learn more](../usage/fetcher.md) about using `ApiReportFetcher`.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
If your API client returns lists of structures that are not similar to dictionaries,
2+
you might need to implement a custom parser based on `garf_core.parsers.BaseParser`.
3+
4+
5+
## Define Parser class
6+
7+
The only method you need to implement is `parse_row`.
8+
9+
```python
10+
from garf_core.parsers import BaseParser
11+
12+
class MyParser(BaseParser):
13+
14+
def parse_row(row):
15+
# Your parsing logic here
16+
```
17+
18+
## Use with ApiReportFetcher
19+
20+
Once your Parser class is defined, you can use with built-in `ApiReportFetcher`.
21+
22+
```python
23+
from garf_core import ApiReportFetcher
24+
25+
report_fetcher = ApiReportFetcher(api_client, parser=MyParser)
26+
```
27+
!!! note
28+
[Learn more](../usage/fetcher.md) about using `ApiReportFetcher`.

docs/customization/creating-your-own-libraries.md renamed to docs/development/create-garf-library.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# How to create your own garf-based library
22

33

4-
In order to create your own library follow the steps below:
4+
In order to create your own library follow the steps.
55

6-
## Project setup
6+
## Setup project
77
* Create a folder `<YOUR-LIBRARY>` folder with a subfolder `garf_<YOUR_LIBRARY>_api` name.
88

99
* Create `pyproject.toml` of the following structure:
@@ -42,7 +42,7 @@ classifiers = [
4242
your-api = "your_library.report_fetcher"
4343
```
4444

45-
## Mandatory classes
45+
## Implement classes
4646

4747
Go to `garf_<YOUR_LIBRARY>_api` folder and create `report_fetcher.py` file.
4848

@@ -89,7 +89,7 @@ class MyLibraryApiReportFetcher(report_fetcher.ApiReportFetcher):
8989
```
9090

9191

92-
## Installing
92+
## Install
9393

9494
To test your project install is as an editable package.
9595
```bash
@@ -100,7 +100,7 @@ pip install -e .
100100
!!! note
101101
If you thing others will benefit from your package you can now upload your project to pypi.
102102

103-
## Running
103+
## Run
104104

105105
Once you installed the package you can run it with `garf` utility:
106106

docs/development/overview.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Build on top of garf
2+
3+
You can create on top of `garf` you can have a couple of options:
4+
5+
* [Create an API client](create-api-client.md) to be used with `ApiReportFetcher` class
6+
* [Create an API response parser](create-api-response-parser.md) to be customize how `ApiReportFetcher` parses response from API
7+
* [Create a library](create-garf-library.md) and expose it as `garf` plugin.

docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
`garf` is a framework for building various connectors to reporting API that provides
66
users with a SQL-like interface to specify what needs to be extracted from the API.
77

8-
It allows you to define SQL-like queries alongside aliases and custom extractors and specify where the results of such query should be stored.\
8+
It allows you to define SQL-like queries alongside aliases and custom extractors
9+
and specify where the results of such query should be stored.
910
Based on a query `garf` builds the correct request to a reporting API, parses response
1011
and transform it into a structure suitable for writing data.
1112

docs/usage/api-client.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# API clients
2+
3+
ApiClient is responsible for sending request to an API based on the query.
4+
5+
## Built-in API clients
6+
7+
### Fake
8+
9+
`FakeApiClient` is ideal for prototyping and test.
10+
11+
It allows you to specify sample response from an API as JSON or a dictionary.
12+
13+
```python
14+
from garf_core.api_clients import FakeApiClient
15+
16+
fake_data = [
17+
{'field1': {'subfield': 1}, 'field2': 2},
18+
{'field1': {'subfield': 10}, 'field2': 2},
19+
]
20+
api_client = FakeApiClient(results=fake_data)
21+
```
22+
23+
Instead of providing data as a variable you can read them from JSON or CSV.
24+
25+
```python
26+
from garf_core.api_clients import FakeApiClient
27+
28+
api_client = FakeApiClient.from_json('path/to/json')
29+
api_client = FakeApiClient.from_csv('path/to/csv')
30+
```
31+
!!! note
32+
You can simplify fetching fake data with [`FakeApiReportFetcher`](fetcher.md#fake).
33+
34+
35+
### REST
36+
37+
REST API client is useful when you have a REST API available. Provide endpoint
38+
to get data from.
39+
40+
```python
41+
from garf_core.api_clients import RestApiClient
42+
43+
endpoint= 'https://api.restful-api.dev'
44+
api_client = RestApiClient(endpoint=endpoint)
45+
```
46+
47+
!!! note
48+
You can simplify fetching from REST API with [`RestApiReportFetcher`](fetcher.md#rest).
49+
50+
## Create API client
51+
52+
If you want to get reports from different APIs you need to implement new API Client.
53+
54+
Please refer to [development docs](../development/create-api-client.md) to learn more.

docs/usage/fetcher.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# ApiReportFetcher
2+
3+
## Initialization
4+
5+
### Api Client
6+
To initialize `ApiReportFetcher` you need an instance of an API client to
7+
interact with an API. You can choose from [built-in](api-client.md) API clients
8+
or [create](../development/create-api-client.md) your own.
9+
```python
10+
from garf_core import ApiReportFetcher
11+
12+
report_fetcher = ApiReportFetcher(api_client)
13+
```
14+
15+
16+
### Parser
17+
18+
Under the hood `ApiReportFetcher` fetches data from an API as list of
19+
dictionaries and tries to access elements in each dictionary via `DictParser`.
20+
21+
You can overwrite this behaviour by using one of [built-in parsers](parsers.md)
22+
or [implementing](../development/create-api-response-parser.md) your own.
23+
24+
Suppose you want to use `NumericConverterDictParser` to automatically convert
25+
strings to int/float whenever possible.
26+
27+
28+
```python
29+
from garf_core import ApiReportFetcher
30+
from garf_core.parsers import NumericConverterDictParser
31+
32+
report_fetcher = ApiReportFetcher(
33+
api_client=api_client,
34+
parser=NumericConverterDictParser
35+
)
36+
```
37+
38+
39+
### Built-in queries
40+
41+
Some queries for a particular API can be quite common so you want to create one
42+
or several [built-in queries](queries.md#built-in-queries).
43+
44+
You can specified them in `builtin_queries` parameters during `ApiReportFetcher`
45+
initialization.
46+
47+
```python
48+
from garf_core import ApiReportFetcher
49+
from garf_core.report import GarfReport
50+
from garf_core.parsers import NumericConverterDictParser
51+
52+
def builtin_query(fetcher: ApiReportFetcher) -> GarfReport:
53+
return fetcher.fetch('SELECT field FROM resource')
54+
55+
56+
builtin_queries = {'my_query': builtin_query}
57+
58+
report_fetcher = ApiReportFetcher(
59+
api_client=api_client,
60+
builtin_queries=builtin_queries
61+
)
62+
```
63+
64+
65+
## Fetching
66+
To fetch data from an API use `fetch` method.
67+
68+
```python
69+
from garf_core import ApiReportFetcher
70+
71+
report_fetcher = ApiReportFetcher(api_client)
72+
query = 'SELECT metric FROM resource'
73+
report = report_fetcher.fetch(query)
74+
```
75+
76+
`fetch` method returns `GarfReport` which can be [processed](reports.md) in Python
77+
or [written](writers.md) to local / remote storage.
78+
79+
### Parametrization
80+
81+
If your query contains [macros](queries.md/#macros) or [templates](queries.md#templates), you need to pass values for them via `args` parameters.
82+
83+
```python
84+
from garf_core import ApiReportFetcher
85+
from garf_core.query_editor import GarfQueryParameters
86+
87+
report_fetcher = ApiReportFetcher(api_client)
88+
89+
query = 'SELECT metric FROM resource WHERE dimension={dimension}'
90+
91+
query_parameters = GarfQueryParameters(
92+
macro={'dimension': 'value'},
93+
template={'dimension': 'value'},
94+
)
95+
96+
report = report_fetcher.fetch(query, args=query_parameters)
97+
```
98+
99+
!!! note
100+
You can pass a dictionary instead of `GarfQueryParameters`.
101+
102+
```python
103+
104+
query_parameters = {
105+
'macro': {
106+
'dimension': 'value',
107+
},
108+
'template': {
109+
'dimension': 'value',
110+
}
111+
}
112+
113+
report = report_fetcher.fetch(query, args=query_parameters)
114+
```
115+
116+
117+
## Built-in report fetchers
118+
119+
To simplify testing and working with REST APIs `garf` has two built-in report fetchers:
120+
121+
* `FakeApiReportFetcher` - simulates API response based on provided data
122+
* `RestApiReportFetcher` - interacts with APIs with REST interface.
123+
124+
### Fake
125+
126+
`FakeApiReportFetcher` is based on [`FakeApiClient`](api-client.md#fake).
127+
128+
It's ideal for prototyping and testing APIs without interacting with them directly.
129+
130+
```python
131+
from garf_core.fetchers import FakeApiReportFetcher
132+
133+
fake_data = [
134+
{'field1': {'subfield': 1}, 'field2': 2},
135+
{'field1': {'subfield': 10}, 'field2': 2},
136+
]
137+
report_fetcher = FakeApiReportFetcher.from_data(fake_data)
138+
139+
query = 'SELECT field1.subfield AS column FROM resource'
140+
report = report_fetcher.fetch(query)
141+
```
142+
143+
!!!note
144+
Instead providing data directly you can use helper methods - `from_json` and `from_csv`:
145+
146+
```python
147+
report_fetcher = FakeApiReportFetcher.from_json('path/to/json')
148+
report_fetcher = FakeApiReportFetcher.from_csv('path/to/csv')
149+
150+
```
151+
152+
### Rest
153+
154+
`RestApiReportFetcher` is based on [`RestApiClient`](api-client.md#rest).
155+
156+
It's can be used with any API that provides REST interface.
157+
158+
You need to provide `endpoint` parameter which specifies root level address where
159+
API exists.
160+
161+
When writing queries specify relative address of the resource you want to fetch from
162+
(i.e. `customers/1/orders`).
163+
164+
```python
165+
from garf_core.fetchers import RestApiReportFetcher
166+
167+
endpoint= 'https://api.restful-api.dev'
168+
report_fetcher = RestApiReportFetcher.from_endpoint(endpoint)
169+
170+
query = 'SELECT id FROM objects'
171+
report = report_fetcher.fetch(query)
172+
```

docs/usage/parsers.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# API Response parsers
2+
3+
Once ApiClient returns response from an API it's Parser's job to convert it to
4+
the format consumable by `GarfReport`.
5+
6+
## Built-in parsers
7+
8+
* `DictParser` - return an element from API following the attribute path.
9+
* `NumericConverterDictParser` - subclass of `DictParser` but tries to convert strings to int/float if possible.
10+
11+
!!! important
12+
If Parser fails to find an element if returns `None`.
13+
14+
## Create parsers
15+
16+
If response from your API is in the format not supported by built-in parsers you
17+
can build you own parser.
18+
19+
Please refer to [development docs](../development/create-api-response-parser.md) to learn more.

docs/usage/queries.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ query = Campaigns(status='DISABLED')
292292

293293
```python
294294
from garf_core.base_query import BaseQuery
295-
from garf_io import reader
296295

297296
class Campaigns(BaseQuery):
298297
query_text = """

0 commit comments

Comments
 (0)