garf-executors 0.0.3__tar.gz → 0.0.6__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.
Potentially problematic release.
This version of garf-executors might be problematic. Click here for more details.
- {garf_executors-0.0.3 → garf_executors-0.0.6}/PKG-INFO +9 -4
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/__init__.py +1 -1
- garf_executors-0.0.6/garf_executors/api_executor.py +132 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/cli.py +15 -27
- garf_executors-0.0.6/garf_executors/entrypoints/server.py +65 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/utils.py +20 -25
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/sql_executor.py +7 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/PKG-INFO +9 -4
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/SOURCES.txt +1 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/requires.txt +6 -1
- {garf_executors-0.0.3 → garf_executors-0.0.6}/pyproject.toml +8 -1
- garf_executors-0.0.3/garf_executors/api_executor.py +0 -98
- {garf_executors-0.0.3 → garf_executors-0.0.6}/README.md +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/bq_executor.py +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/__init__.py +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/exceptions.py +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/fetchers.py +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/dependency_links.txt +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/entry_points.txt +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/top_level.txt +0 -0
- {garf_executors-0.0.3 → garf_executors-0.0.6}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: garf-executors
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Executes queries against API and writes data to local/remote storage.
|
|
5
|
-
Author-email: "Google Inc. (gTech gPS CSE team)" <no-reply@google.com>
|
|
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
|
|
7
7
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -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
|
|
@@ -18,14 +19,18 @@ Requires-Python: >=3.8
|
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
Requires-Dist: garf-core
|
|
20
21
|
Requires-Dist: garf-io
|
|
22
|
+
Requires-Dist: pyyaml
|
|
23
|
+
Requires-Dist: pydantic
|
|
21
24
|
Provides-Extra: bq
|
|
22
25
|
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
26
|
Requires-Dist: pandas; extra == "bq"
|
|
24
27
|
Provides-Extra: sql
|
|
25
28
|
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
29
|
Requires-Dist: pandas; extra == "sql"
|
|
30
|
+
Provides-Extra: server
|
|
31
|
+
Requires-Dist: fastapi[standard]; extra == "server"
|
|
27
32
|
Provides-Extra: all
|
|
28
|
-
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
33
|
+
Requires-Dist: garf-executors[bq,server,sql]; extra == "all"
|
|
29
34
|
|
|
30
35
|
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
31
36
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Copyright 2024 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 executing Garf queries and writing them to local/remote.
|
|
15
|
+
|
|
16
|
+
ApiQueryExecutor performs fetching data from API in a form of
|
|
17
|
+
GarfReport and saving it to local/remote storage.
|
|
18
|
+
"""
|
|
19
|
+
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
import pydantic
|
|
26
|
+
|
|
27
|
+
from garf_core import query_editor, report_fetcher
|
|
28
|
+
from garf_executors import exceptions
|
|
29
|
+
from garf_io import writer
|
|
30
|
+
from garf_io.writers import abs_writer
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ApiExecutionContext(pydantic.BaseModel):
|
|
36
|
+
"""Common context for executing one or more queries.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
query_parameters: Parameters to dynamically change query text.
|
|
40
|
+
fetcher_parameters: Parameters to specify fetching setup.
|
|
41
|
+
writer: Type of writer to use.
|
|
42
|
+
writer_parameters: Optional parameters to setup writer.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
query_parameters: query_editor.GarfQueryParameters | None = None
|
|
46
|
+
fetcher_parameters: dict[str, str] | None = None
|
|
47
|
+
writer: str = 'console'
|
|
48
|
+
writer_parameters: dict[str, str] | None = None
|
|
49
|
+
|
|
50
|
+
def model_post_init(self, __context__) -> None:
|
|
51
|
+
if self.fetcher_parameters is None:
|
|
52
|
+
self.fetcher_parameters = {}
|
|
53
|
+
if self.writer_parameters is None:
|
|
54
|
+
self.writer_parameters = {}
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def writer_client(self) -> abs_writer.AbsWriter:
|
|
58
|
+
writer_client = writer.create_writer(self.writer, **self.writer_parameters)
|
|
59
|
+
if self.writer == 'bq':
|
|
60
|
+
_ = writer_client.create_or_get_dataset()
|
|
61
|
+
if self.writer == 'sheet':
|
|
62
|
+
writer_client.init_client()
|
|
63
|
+
return writer_client
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ApiQueryExecutor:
|
|
67
|
+
"""Gets data from API and writes them to local/remote storage.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
api_client: a client used for connecting to API.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, fetcher: report_fetcher.ApiReportFetcher) -> None:
|
|
74
|
+
"""Initializes ApiQueryExecutor.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
fetcher: Instantiated report fetcher.
|
|
78
|
+
"""
|
|
79
|
+
self.fetcher = fetcher
|
|
80
|
+
|
|
81
|
+
async def aexecute(
|
|
82
|
+
self, query: str, context: ApiExecutionContext, **kwargs: str
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Reads query, extract results and stores them in a specified location.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
query: Location of the query.
|
|
88
|
+
context: Query execution context.
|
|
89
|
+
"""
|
|
90
|
+
self.execute(query, context, **kwargs)
|
|
91
|
+
|
|
92
|
+
def execute(
|
|
93
|
+
self,
|
|
94
|
+
query: str,
|
|
95
|
+
title: str,
|
|
96
|
+
context: ApiExecutionContext,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Reads query, extract results and stores them in a specified location.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
query: Location of the query.
|
|
102
|
+
title: Name of the query.
|
|
103
|
+
context: Query execution context.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
GarfExecutorError: When failed to execute query.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
logger.debug('starting query %s', query)
|
|
110
|
+
results = self.fetcher.fetch(
|
|
111
|
+
query_specification=query,
|
|
112
|
+
args=context.query_parameters,
|
|
113
|
+
**context.fetcher_parameters,
|
|
114
|
+
)
|
|
115
|
+
writer_client = context.writer_client
|
|
116
|
+
logger.debug(
|
|
117
|
+
'Start writing data for query %s via %s writer',
|
|
118
|
+
title,
|
|
119
|
+
type(writer_client),
|
|
120
|
+
)
|
|
121
|
+
writer_client.write(results, title)
|
|
122
|
+
logger.debug(
|
|
123
|
+
'Finish writing data for query %s via %s writer',
|
|
124
|
+
title,
|
|
125
|
+
type(writer_client),
|
|
126
|
+
)
|
|
127
|
+
logger.info('%s executed successfully', title)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error('%s generated an exception: %s', title, str(e))
|
|
130
|
+
raise exceptions.GarfExecutorError(
|
|
131
|
+
'%s generated an exception: %s', title, str(e)
|
|
132
|
+
) from e
|
|
@@ -20,14 +20,13 @@ storage.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import argparse
|
|
23
|
-
import functools
|
|
24
23
|
import sys
|
|
25
24
|
from concurrent import futures
|
|
26
25
|
|
|
27
26
|
import garf_executors
|
|
28
27
|
from garf_executors import exceptions
|
|
29
28
|
from garf_executors.entrypoints import utils
|
|
30
|
-
from garf_io import reader
|
|
29
|
+
from garf_io import reader
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def main():
|
|
@@ -35,7 +34,7 @@ def main():
|
|
|
35
34
|
parser.add_argument('query', nargs='*')
|
|
36
35
|
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
37
36
|
parser.add_argument('--source', dest='source', default=None)
|
|
38
|
-
parser.add_argument('--output', dest='output', default=
|
|
37
|
+
parser.add_argument('--output', dest='output', default='console')
|
|
39
38
|
parser.add_argument('--input', dest='input', default='file')
|
|
40
39
|
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
41
40
|
parser.add_argument('--logger', dest='logger', default='local')
|
|
@@ -72,7 +71,6 @@ def main():
|
|
|
72
71
|
raise exceptions.GarfExecutorError(
|
|
73
72
|
'Please provide one or more queries to run'
|
|
74
73
|
)
|
|
75
|
-
|
|
76
74
|
config = utils.ConfigBuilder('garf').build(vars(args), kwargs)
|
|
77
75
|
logger.debug('config: %s', config)
|
|
78
76
|
|
|
@@ -81,17 +79,18 @@ def main():
|
|
|
81
79
|
logger.debug('initialized config: %s', config)
|
|
82
80
|
|
|
83
81
|
extra_parameters = utils.ParamsParser(['source']).parse(kwargs)
|
|
84
|
-
|
|
85
|
-
concrete_api_fetcher()
|
|
86
|
-
)
|
|
82
|
+
source_parameters = extra_parameters.get('source', {})
|
|
87
83
|
reader_client = reader.create_reader(args.input)
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
context = garf_executors.api_executor.ApiExecutionContext(
|
|
86
|
+
query_parameters=config.params,
|
|
87
|
+
writer=args.output,
|
|
88
|
+
writer_parameters=config.writer_params,
|
|
89
|
+
fetcher_parameters=source_parameters,
|
|
90
|
+
)
|
|
91
|
+
query_executor = garf_executors.api_executor.ApiQueryExecutor(
|
|
92
|
+
concrete_api_fetcher(**source_parameters)
|
|
93
|
+
)
|
|
95
94
|
if args.parallel_queries:
|
|
96
95
|
logger.info('Running queries in parallel')
|
|
97
96
|
with futures.ThreadPoolExecutor(args.parallel_threshold) as executor:
|
|
@@ -100,27 +99,16 @@ def main():
|
|
|
100
99
|
query_executor.execute,
|
|
101
100
|
reader_client.read(query),
|
|
102
101
|
query,
|
|
103
|
-
|
|
104
|
-
config.params,
|
|
105
|
-
**extra_parameters.get('source', {}),
|
|
102
|
+
context,
|
|
106
103
|
): query
|
|
107
104
|
for query in args.query
|
|
108
105
|
}
|
|
109
106
|
for future in futures.as_completed(future_to_query):
|
|
110
|
-
|
|
111
|
-
utils.garf_runner(query, future.result, logger)
|
|
107
|
+
future.result()
|
|
112
108
|
else:
|
|
113
109
|
logger.info('Running queries sequentially')
|
|
114
110
|
for query in args.query:
|
|
115
|
-
|
|
116
|
-
query_executor.execute,
|
|
117
|
-
reader_client.read(query),
|
|
118
|
-
query,
|
|
119
|
-
writer_client,
|
|
120
|
-
config.params,
|
|
121
|
-
**extra_parameters.get('source', {}),
|
|
122
|
-
)
|
|
123
|
-
utils.garf_runner(query, callback, logger)
|
|
111
|
+
query_executor.execute(reader_client.read(query), query, context)
|
|
124
112
|
|
|
125
113
|
|
|
126
114
|
if __name__ == '__main__':
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
"""FastAPI endpoint for executing queries."""
|
|
16
|
+
|
|
17
|
+
import fastapi
|
|
18
|
+
import pydantic
|
|
19
|
+
import uvicorn
|
|
20
|
+
|
|
21
|
+
import garf_executors
|
|
22
|
+
from garf_executors import exceptions
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApiExecutorRequest(pydantic.BaseModel):
|
|
26
|
+
"""Request for executing a query.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
source: Type of API to interact with.
|
|
30
|
+
query: Query to execute.
|
|
31
|
+
title: Name of the query used as an output for writing.
|
|
32
|
+
context: Execution context.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
source: str
|
|
36
|
+
query: str
|
|
37
|
+
title: str
|
|
38
|
+
context: garf_executors.api_executor.ApiExecutionContext
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
router = fastapi.APIRouter(prefix='/api')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.post('/execute')
|
|
45
|
+
async def execute(request: ApiExecutorRequest) -> dict[str, str]:
|
|
46
|
+
if not (concrete_api_fetcher := garf_executors.FETCHERS.get(request.source)):
|
|
47
|
+
raise exceptions.GarfExecutorError(
|
|
48
|
+
f'Source {request.source} is not available.'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
query_executor = garf_executors.api_executor.ApiQueryExecutor(
|
|
52
|
+
concrete_api_fetcher(**request.context.fetcher_parameters)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
query_executor.execute(request.query, request.title, request.context)
|
|
56
|
+
|
|
57
|
+
return fastapi.responses.JSONResponse(
|
|
58
|
+
content=fastapi.encoders.jsonable_encoder({'result': 'success'})
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
app = fastapi.FastAPI()
|
|
64
|
+
app.include_router(router)
|
|
65
|
+
uvicorn.run(app)
|
|
@@ -20,9 +20,8 @@ import datetime
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
22
|
import sys
|
|
23
|
-
import traceback
|
|
24
23
|
from collections.abc import MutableSequence, Sequence
|
|
25
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, TypedDict
|
|
26
25
|
|
|
27
26
|
import smart_open
|
|
28
27
|
import yaml
|
|
@@ -298,21 +297,36 @@ class ParamsParser:
|
|
|
298
297
|
key = param[0]
|
|
299
298
|
if not identifier or identifier not in key:
|
|
300
299
|
return None
|
|
301
|
-
provided_identifier,
|
|
300
|
+
provided_identifier, *keys = key.split('.')
|
|
301
|
+
if len(keys) > 1:
|
|
302
|
+
raise GarfParamsException(
|
|
303
|
+
f'{key} is invalid format,'
|
|
304
|
+
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
305
|
+
'are the correct formats'
|
|
306
|
+
)
|
|
302
307
|
provided_identifier = provided_identifier.replace('--', '')
|
|
303
308
|
if provided_identifier not in self.identifiers:
|
|
304
309
|
raise GarfParamsException(
|
|
305
310
|
f'CLI argument {provided_identifier} is not supported'
|
|
306
|
-
f
|
|
311
|
+
f', supported arguments {", ".join(self.identifiers)}'
|
|
307
312
|
)
|
|
308
313
|
if provided_identifier != identifier:
|
|
309
314
|
return None
|
|
310
|
-
key =
|
|
315
|
+
key = keys[0].replace('-', '_')
|
|
316
|
+
if not key:
|
|
317
|
+
raise GarfParamsException(
|
|
318
|
+
f'{identifier} {key} is invalid,'
|
|
319
|
+
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
320
|
+
'are the correct formats'
|
|
321
|
+
)
|
|
311
322
|
if len(param) == 2:
|
|
312
323
|
return {key: param[1]}
|
|
324
|
+
if len(param) == 1:
|
|
325
|
+
return {key: True}
|
|
313
326
|
raise GarfParamsException(
|
|
314
327
|
f'{identifier} {key} is invalid,'
|
|
315
|
-
f'
|
|
328
|
+
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
329
|
+
'are the correct formats'
|
|
316
330
|
)
|
|
317
331
|
|
|
318
332
|
|
|
@@ -429,25 +443,6 @@ def _remove_empty_values(dict_object: dict[str, Any]) -> dict[str, Any]:
|
|
|
429
443
|
return dict_object
|
|
430
444
|
|
|
431
445
|
|
|
432
|
-
def garf_runner(query: str, callback: Callable, logger) -> None:
|
|
433
|
-
try:
|
|
434
|
-
logger.debug('starting query %s', query)
|
|
435
|
-
callback()
|
|
436
|
-
logger.info('%s executed successfully', query)
|
|
437
|
-
except Exception as e:
|
|
438
|
-
traceback.print_tb(e.__traceback__)
|
|
439
|
-
logger.error('%s generated an exception: %s', query, str(e))
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
def postprocessor_runner(query: str, callback: Callable, logger) -> None:
|
|
443
|
-
try:
|
|
444
|
-
logger.debug('starting query %s', query)
|
|
445
|
-
callback()
|
|
446
|
-
logger.info('%s executed successfully', query)
|
|
447
|
-
except Exception as e:
|
|
448
|
-
logger.error('%s generated an exception: %s', query, str(e))
|
|
449
|
-
|
|
450
|
-
|
|
451
446
|
def init_logging(
|
|
452
447
|
loglevel: str = 'INFO', logger_type: str = 'local', name: str = __name__
|
|
453
448
|
) -> logging.Logger:
|
|
@@ -47,6 +47,13 @@ class SqlAlchemyQueryExecutor(query_editor.TemplateProcessorMixin):
|
|
|
47
47
|
"""
|
|
48
48
|
self.engine = engine
|
|
49
49
|
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_connection_string(
|
|
52
|
+
cls, connection_string: str
|
|
53
|
+
) -> SqlAlchemyQueryExecutor:
|
|
54
|
+
engine = sqlalchemy.create_engine(connection_string)
|
|
55
|
+
return cls(engine)
|
|
56
|
+
|
|
50
57
|
def execute(
|
|
51
58
|
self,
|
|
52
59
|
script_name: str | None,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: garf-executors
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Executes queries against API and writes data to local/remote storage.
|
|
5
|
-
Author-email: "Google Inc. (gTech gPS CSE team)" <no-reply@google.com>
|
|
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
|
|
7
7
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -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
|
|
@@ -18,14 +19,18 @@ Requires-Python: >=3.8
|
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
Requires-Dist: garf-core
|
|
20
21
|
Requires-Dist: garf-io
|
|
22
|
+
Requires-Dist: pyyaml
|
|
23
|
+
Requires-Dist: pydantic
|
|
21
24
|
Provides-Extra: bq
|
|
22
25
|
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
26
|
Requires-Dist: pandas; extra == "bq"
|
|
24
27
|
Provides-Extra: sql
|
|
25
28
|
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
29
|
Requires-Dist: pandas; extra == "sql"
|
|
30
|
+
Provides-Extra: server
|
|
31
|
+
Requires-Dist: fastapi[standard]; extra == "server"
|
|
27
32
|
Provides-Extra: all
|
|
28
|
-
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
33
|
+
Requires-Dist: garf-executors[bq,server,sql]; extra == "all"
|
|
29
34
|
|
|
30
35
|
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
31
36
|
|
|
@@ -7,9 +7,12 @@ name = "garf-executors"
|
|
|
7
7
|
dependencies = [
|
|
8
8
|
"garf-core",
|
|
9
9
|
"garf-io",
|
|
10
|
+
"pyyaml",
|
|
11
|
+
"pydantic",
|
|
10
12
|
]
|
|
11
13
|
authors = [
|
|
12
14
|
{name = "Google Inc. (gTech gPS CSE team)", email = "no-reply@google.com"},
|
|
15
|
+
{name = "Andrei Markin", email = "andrey.markin.ppc@gmail.com"},
|
|
13
16
|
]
|
|
14
17
|
requires-python = ">=3.8"
|
|
15
18
|
description = "Executes queries against API and writes data to local/remote storage."
|
|
@@ -22,6 +25,7 @@ classifiers = [
|
|
|
22
25
|
"Programming Language :: Python :: 3.10",
|
|
23
26
|
"Programming Language :: Python :: 3.11",
|
|
24
27
|
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
25
29
|
"Intended Audience :: Developers",
|
|
26
30
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
31
|
"Operating System :: OS Independent",
|
|
@@ -41,8 +45,11 @@ sql=[
|
|
|
41
45
|
"garf-io[sqlalchemy]",
|
|
42
46
|
"pandas",
|
|
43
47
|
]
|
|
48
|
+
server=[
|
|
49
|
+
"fastapi[standard]",
|
|
50
|
+
]
|
|
44
51
|
all = [
|
|
45
|
-
"garf-executors[bq,sql]"
|
|
52
|
+
"garf-executors[bq,sql,server]"
|
|
46
53
|
]
|
|
47
54
|
[project.scripts]
|
|
48
55
|
garf="garf_executors.entrypoints.cli:main"
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Copyright 2024 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 executing Garf queries and writing them to local/remote.
|
|
15
|
-
|
|
16
|
-
ApiQueryExecutor performs fetching data from API in a form of
|
|
17
|
-
GarfReport and saving it to local/remote storage.
|
|
18
|
-
"""
|
|
19
|
-
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
|
20
|
-
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import logging
|
|
24
|
-
|
|
25
|
-
from garf_core import report_fetcher
|
|
26
|
-
from garf_io.writers import abs_writer, console_writer
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class ApiQueryExecutor:
|
|
32
|
-
"""Gets data from API and writes them to local/remote storage.
|
|
33
|
-
|
|
34
|
-
Attributes:
|
|
35
|
-
api_client: a client used for connecting to API.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, fetcher: report_fetcher.ApiReportFetcher) -> None:
|
|
39
|
-
"""Initializes QueryExecutor.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
fetcher: Instantiated report fetcher.
|
|
43
|
-
"""
|
|
44
|
-
self.fetcher = fetcher
|
|
45
|
-
|
|
46
|
-
async def aexecute(
|
|
47
|
-
self,
|
|
48
|
-
query_text: str,
|
|
49
|
-
query_name: str,
|
|
50
|
-
writer_client: abs_writer.AbsWriter = console_writer.ConsoleWriter(),
|
|
51
|
-
args: dict[str, str] | None = None,
|
|
52
|
-
**kwargs: str,
|
|
53
|
-
) -> None:
|
|
54
|
-
"""Reads query, extract results and stores them in a specified location.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
query_text: Text for the query.
|
|
58
|
-
query_name: Identifier of a query.
|
|
59
|
-
customer_ids: All accounts for which query will be executed.
|
|
60
|
-
writer_client: Client responsible for writing data to local/remote
|
|
61
|
-
location.
|
|
62
|
-
args: Arguments that need to be passed to the query.
|
|
63
|
-
optimize_performance: strategy for speeding up query execution
|
|
64
|
-
("NONE", "PROTOBUF", "BATCH", "BATCH_PROTOBUF").
|
|
65
|
-
"""
|
|
66
|
-
self.execute(query_text, query_name, writer_client, args, **kwargs)
|
|
67
|
-
|
|
68
|
-
def execute(
|
|
69
|
-
self,
|
|
70
|
-
query_text: str,
|
|
71
|
-
query_name: str,
|
|
72
|
-
writer_client: abs_writer.AbsWriter = console_writer.ConsoleWriter(),
|
|
73
|
-
args: dict[str, str] | None = None,
|
|
74
|
-
**kwargs: str,
|
|
75
|
-
) -> None:
|
|
76
|
-
"""Reads query, extract results and stores them in a specified location.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
query_text: Text for the query.
|
|
80
|
-
query_name: Identifier of a query.
|
|
81
|
-
writer_client: Client responsible for writing data to local/remote
|
|
82
|
-
location.
|
|
83
|
-
args: Arguments that need to be passed to the query.
|
|
84
|
-
"""
|
|
85
|
-
results = self.fetcher.fetch(
|
|
86
|
-
query_specification=query_text, args=args, **kwargs
|
|
87
|
-
)
|
|
88
|
-
logger.debug(
|
|
89
|
-
'Start writing data for query %s via %s writer',
|
|
90
|
-
query_name,
|
|
91
|
-
type(writer_client),
|
|
92
|
-
)
|
|
93
|
-
writer_client.write(results, query_name)
|
|
94
|
-
logger.debug(
|
|
95
|
-
'Finish writing data for query %s via %s writer',
|
|
96
|
-
query_name,
|
|
97
|
-
type(writer_client),
|
|
98
|
-
)
|
|
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
|