garf-executors 0.0.1__tar.gz → 0.0.3__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_executors-0.0.3/PKG-INFO +61 -0
- garf_executors-0.0.3/README.md +32 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/__init__.py +4 -0
- garf_executors-0.0.3/garf_executors/entrypoints/cli.py +127 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/entrypoints/utils.py +4 -2
- garf_executors-0.0.3/garf_executors/exceptions.py +17 -0
- garf_executors-0.0.3/garf_executors/fetchers.py +37 -0
- garf_executors-0.0.3/garf_executors.egg-info/PKG-INFO +61 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors.egg-info/SOURCES.txt +4 -6
- garf_executors-0.0.3/garf_executors.egg-info/entry_points.txt +2 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/pyproject.toml +5 -3
- garf_executors-0.0.1/PKG-INFO +0 -30
- garf_executors-0.0.1/README.md +0 -1
- garf_executors-0.0.1/garf_executors/entrypoints/cli/__init__.py +0 -0
- garf_executors-0.0.1/garf_executors/entrypoints/cli/api.py +0 -213
- garf_executors-0.0.1/garf_executors/entrypoints/cli/bq.py +0 -112
- garf_executors-0.0.1/garf_executors/entrypoints/cli/gaarf.py +0 -213
- garf_executors-0.0.1/garf_executors/entrypoints/cli/sql.py +0 -109
- garf_executors-0.0.1/garf_executors.egg-info/PKG-INFO +0 -30
- garf_executors-0.0.1/garf_executors.egg-info/entry_points.txt +0 -3
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/api_executor.py +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/bq_executor.py +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/entrypoints/__init__.py +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors/sql_executor.py +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors.egg-info/dependency_links.txt +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors.egg-info/requires.txt +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/garf_executors.egg-info/top_level.txt +0 -0
- {garf_executors-0.0.1 → garf_executors-0.0.3}/setup.cfg +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: garf-executors
|
|
3
|
+
Version: 0.0.3
|
|
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>
|
|
6
|
+
License: Apache 2.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: garf-core
|
|
20
|
+
Requires-Dist: garf-io
|
|
21
|
+
Provides-Extra: bq
|
|
22
|
+
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
|
+
Requires-Dist: pandas; extra == "bq"
|
|
24
|
+
Provides-Extra: sql
|
|
25
|
+
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
|
+
Requires-Dist: pandas; extra == "sql"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
29
|
+
|
|
30
|
+
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
31
|
+
|
|
32
|
+
`garf-executors` is responsible for orchestrating process of fetching from API and storing data in a storage.
|
|
33
|
+
|
|
34
|
+
Currently the following executors are supports:
|
|
35
|
+
|
|
36
|
+
* `ApiExecutor` - fetching data from reporting API and saves it to a requested destination.
|
|
37
|
+
* `BigQueryExecutor` - executes SQL code in BigQuery.
|
|
38
|
+
* `SqlExecutor` - executes SQL code in a SqlAlchemy supported DB.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
`pip install garf-executors`
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
After `garf-executors` is installed you can use `garf` utility to perform fetching.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
garf <QUERIES> --source <API_SOURCE> \
|
|
50
|
+
--output <OUTPUT_TYPE> \
|
|
51
|
+
--source.params1=<VALUE>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
where
|
|
55
|
+
|
|
56
|
+
* `<QUERIES>`- local or remote path(s) to files with queries.
|
|
57
|
+
* `<API_SOURCE>`- type of API to use. Based on that the appropriate report fetcher will be initialized.
|
|
58
|
+
* `<OUTPUT_TYPE>` - output supported by [`garf-io` library](../garf_io/README.md).
|
|
59
|
+
|
|
60
|
+
If your report fetcher requires additional parameters you can pass them via key value pairs under `--source.` argument, i.e.`--source.regionCode='US'` - to get data only from *US*.
|
|
61
|
+
> Concrete `--source` parameters are dependent on a particular report fetcher and should be looked up in a documentation for this fetcher.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
2
|
+
|
|
3
|
+
`garf-executors` is responsible for orchestrating process of fetching from API and storing data in a storage.
|
|
4
|
+
|
|
5
|
+
Currently the following executors are supports:
|
|
6
|
+
|
|
7
|
+
* `ApiExecutor` - fetching data from reporting API and saves it to a requested destination.
|
|
8
|
+
* `BigQueryExecutor` - executes SQL code in BigQuery.
|
|
9
|
+
* `SqlExecutor` - executes SQL code in a SqlAlchemy supported DB.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
`pip install garf-executors`
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
After `garf-executors` is installed you can use `garf` utility to perform fetching.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
garf <QUERIES> --source <API_SOURCE> \
|
|
21
|
+
--output <OUTPUT_TYPE> \
|
|
22
|
+
--source.params1=<VALUE>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
where
|
|
26
|
+
|
|
27
|
+
* `<QUERIES>`- local or remote path(s) to files with queries.
|
|
28
|
+
* `<API_SOURCE>`- type of API to use. Based on that the appropriate report fetcher will be initialized.
|
|
29
|
+
* `<OUTPUT_TYPE>` - output supported by [`garf-io` library](../garf_io/README.md).
|
|
30
|
+
|
|
31
|
+
If your report fetcher requires additional parameters you can pass them via key value pairs under `--source.` argument, i.e.`--source.regionCode='US'` - to get data only from *US*.
|
|
32
|
+
> Concrete `--source` parameters are dependent on a particular report fetcher and should be looked up in a documentation for this fetcher.
|
|
@@ -20,7 +20,11 @@ import like this `garf_executors.ApiQueryExecutor`
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
from garf_executors.api_executor import ApiQueryExecutor
|
|
23
|
+
from garf_executors.fetchers import FETCHERS
|
|
23
24
|
|
|
24
25
|
__all__ = [
|
|
26
|
+
'FETCHERS',
|
|
25
27
|
'ApiQueryExecutor',
|
|
26
28
|
]
|
|
29
|
+
|
|
30
|
+
__version__ = '0.0.3'
|
|
@@ -0,0 +1,127 @@
|
|
|
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 `garf` CLI utility.
|
|
15
|
+
|
|
16
|
+
`garf` allows to execute queries and store results in local/remote
|
|
17
|
+
storage.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import functools
|
|
24
|
+
import sys
|
|
25
|
+
from concurrent import futures
|
|
26
|
+
|
|
27
|
+
import garf_executors
|
|
28
|
+
from garf_executors import exceptions
|
|
29
|
+
from garf_executors.entrypoints import utils
|
|
30
|
+
from garf_io import reader, writer
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
parser = argparse.ArgumentParser()
|
|
35
|
+
parser.add_argument('query', nargs='*')
|
|
36
|
+
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
37
|
+
parser.add_argument('--source', dest='source', default=None)
|
|
38
|
+
parser.add_argument('--output', dest='output', default=None)
|
|
39
|
+
parser.add_argument('--input', dest='input', default='file')
|
|
40
|
+
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
41
|
+
parser.add_argument('--logger', dest='logger', default='local')
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
'--parallel-queries', dest='parallel_queries', action='store_true'
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
'--no-parallel-queries', dest='parallel_queries', action='store_false'
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument('--dry-run', dest='dry_run', action='store_true')
|
|
49
|
+
parser.add_argument('-v', '--version', dest='version', action='store_true')
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
'--parallel-threshold', dest='parallel_threshold', default=None, type=int
|
|
52
|
+
)
|
|
53
|
+
parser.set_defaults(parallel_queries=True)
|
|
54
|
+
parser.set_defaults(dry_run=False)
|
|
55
|
+
args, kwargs = parser.parse_known_args()
|
|
56
|
+
|
|
57
|
+
if args.version:
|
|
58
|
+
print(garf_executors.__version__)
|
|
59
|
+
sys.exit()
|
|
60
|
+
if not (source := args.source):
|
|
61
|
+
raise exceptions.GarfExecutorError(
|
|
62
|
+
f'Select one of available sources: {list(garf_executors.FETCHERS.keys())}'
|
|
63
|
+
)
|
|
64
|
+
if not (concrete_api_fetcher := garf_executors.FETCHERS.get(source)):
|
|
65
|
+
raise exceptions.GarfExecutorError(f'Source {source} is not available.')
|
|
66
|
+
|
|
67
|
+
logger = utils.init_logging(
|
|
68
|
+
loglevel=args.loglevel.upper(), logger_type=args.logger
|
|
69
|
+
)
|
|
70
|
+
if not args.query:
|
|
71
|
+
logger.error('Please provide one or more queries to run')
|
|
72
|
+
raise exceptions.GarfExecutorError(
|
|
73
|
+
'Please provide one or more queries to run'
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
config = utils.ConfigBuilder('garf').build(vars(args), kwargs)
|
|
77
|
+
logger.debug('config: %s', config)
|
|
78
|
+
|
|
79
|
+
if config.params:
|
|
80
|
+
config = utils.initialize_runtime_parameters(config)
|
|
81
|
+
logger.debug('initialized config: %s', config)
|
|
82
|
+
|
|
83
|
+
extra_parameters = utils.ParamsParser(['source']).parse(kwargs)
|
|
84
|
+
query_executor = garf_executors.api_executor.ApiQueryExecutor(
|
|
85
|
+
concrete_api_fetcher()
|
|
86
|
+
)
|
|
87
|
+
reader_client = reader.create_reader(args.input)
|
|
88
|
+
|
|
89
|
+
writer_client = writer.create_writer(config.output, **config.writer_params)
|
|
90
|
+
if config.output == 'bq':
|
|
91
|
+
_ = writer_client.create_or_get_dataset()
|
|
92
|
+
if config.output == 'sheet':
|
|
93
|
+
writer_client.init_client()
|
|
94
|
+
|
|
95
|
+
if args.parallel_queries:
|
|
96
|
+
logger.info('Running queries in parallel')
|
|
97
|
+
with futures.ThreadPoolExecutor(args.parallel_threshold) as executor:
|
|
98
|
+
future_to_query = {
|
|
99
|
+
executor.submit(
|
|
100
|
+
query_executor.execute,
|
|
101
|
+
reader_client.read(query),
|
|
102
|
+
query,
|
|
103
|
+
writer_client,
|
|
104
|
+
config.params,
|
|
105
|
+
**extra_parameters.get('source', {}),
|
|
106
|
+
): query
|
|
107
|
+
for query in args.query
|
|
108
|
+
}
|
|
109
|
+
for future in futures.as_completed(future_to_query):
|
|
110
|
+
query = future_to_query[future]
|
|
111
|
+
utils.garf_runner(query, future.result, logger)
|
|
112
|
+
else:
|
|
113
|
+
logger.info('Running queries sequentially')
|
|
114
|
+
for query in args.query:
|
|
115
|
+
callback = functools.partial(
|
|
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)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == '__main__':
|
|
127
|
+
main()
|
|
@@ -299,14 +299,16 @@ class ParamsParser:
|
|
|
299
299
|
if not identifier or identifier not in key:
|
|
300
300
|
return None
|
|
301
301
|
provided_identifier, key = key.split('.')
|
|
302
|
-
|
|
302
|
+
provided_identifier = provided_identifier.replace('--', '')
|
|
303
|
+
if provided_identifier not in self.identifiers:
|
|
303
304
|
raise GarfParamsException(
|
|
304
305
|
f'CLI argument {provided_identifier} is not supported'
|
|
305
306
|
f", supported arguments {', '.join(self.identifiers)}"
|
|
306
307
|
)
|
|
308
|
+
if provided_identifier != identifier:
|
|
309
|
+
return None
|
|
307
310
|
key = key.replace('-', '_')
|
|
308
311
|
if len(param) == 2:
|
|
309
|
-
# TODO: b/337860595 - Ensure that writer params are converted to int
|
|
310
312
|
return {key: param[1]}
|
|
311
313
|
raise GarfParamsException(
|
|
312
314
|
f'{identifier} {key} is invalid,'
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
|
|
16
|
+
class GarfExecutorError(Exception):
|
|
17
|
+
"""Base class for garf executor exceptions."""
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
import inspect
|
|
16
|
+
from importlib.metadata import entry_points
|
|
17
|
+
|
|
18
|
+
from garf_core import report_fetcher
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_report_fetchers() -> dict[str, report_fetcher.ApiReportFetcher]:
|
|
22
|
+
fetchers = entry_points(group='garf')
|
|
23
|
+
found_fetchers = {}
|
|
24
|
+
for fetcher in fetchers:
|
|
25
|
+
try:
|
|
26
|
+
fetcher_module = fetcher.load()
|
|
27
|
+
for name, obj in inspect.getmembers(fetcher_module):
|
|
28
|
+
if inspect.isclass(obj) and issubclass(
|
|
29
|
+
obj, report_fetcher.ApiReportFetcher
|
|
30
|
+
):
|
|
31
|
+
found_fetchers[fetcher.name] = getattr(fetcher_module, name)
|
|
32
|
+
except ModuleNotFoundError:
|
|
33
|
+
continue
|
|
34
|
+
return found_fetchers
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
FETCHERS = get_report_fetchers()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: garf-executors
|
|
3
|
+
Version: 0.0.3
|
|
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>
|
|
6
|
+
License: Apache 2.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: garf-core
|
|
20
|
+
Requires-Dist: garf-io
|
|
21
|
+
Provides-Extra: bq
|
|
22
|
+
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
|
+
Requires-Dist: pandas; extra == "bq"
|
|
24
|
+
Provides-Extra: sql
|
|
25
|
+
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
|
+
Requires-Dist: pandas; extra == "sql"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
29
|
+
|
|
30
|
+
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
31
|
+
|
|
32
|
+
`garf-executors` is responsible for orchestrating process of fetching from API and storing data in a storage.
|
|
33
|
+
|
|
34
|
+
Currently the following executors are supports:
|
|
35
|
+
|
|
36
|
+
* `ApiExecutor` - fetching data from reporting API and saves it to a requested destination.
|
|
37
|
+
* `BigQueryExecutor` - executes SQL code in BigQuery.
|
|
38
|
+
* `SqlExecutor` - executes SQL code in a SqlAlchemy supported DB.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
`pip install garf-executors`
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
After `garf-executors` is installed you can use `garf` utility to perform fetching.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
garf <QUERIES> --source <API_SOURCE> \
|
|
50
|
+
--output <OUTPUT_TYPE> \
|
|
51
|
+
--source.params1=<VALUE>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
where
|
|
55
|
+
|
|
56
|
+
* `<QUERIES>`- local or remote path(s) to files with queries.
|
|
57
|
+
* `<API_SOURCE>`- type of API to use. Based on that the appropriate report fetcher will be initialized.
|
|
58
|
+
* `<OUTPUT_TYPE>` - output supported by [`garf-io` library](../garf_io/README.md).
|
|
59
|
+
|
|
60
|
+
If your report fetcher requires additional parameters you can pass them via key value pairs under `--source.` argument, i.e.`--source.regionCode='US'` - to get data only from *US*.
|
|
61
|
+
> Concrete `--source` parameters are dependent on a particular report fetcher and should be looked up in a documentation for this fetcher.
|
|
@@ -3,6 +3,8 @@ pyproject.toml
|
|
|
3
3
|
garf_executors/__init__.py
|
|
4
4
|
garf_executors/api_executor.py
|
|
5
5
|
garf_executors/bq_executor.py
|
|
6
|
+
garf_executors/exceptions.py
|
|
7
|
+
garf_executors/fetchers.py
|
|
6
8
|
garf_executors/sql_executor.py
|
|
7
9
|
garf_executors.egg-info/PKG-INFO
|
|
8
10
|
garf_executors.egg-info/SOURCES.txt
|
|
@@ -11,9 +13,5 @@ garf_executors.egg-info/entry_points.txt
|
|
|
11
13
|
garf_executors.egg-info/requires.txt
|
|
12
14
|
garf_executors.egg-info/top_level.txt
|
|
13
15
|
garf_executors/entrypoints/__init__.py
|
|
14
|
-
garf_executors/entrypoints/
|
|
15
|
-
garf_executors/entrypoints/
|
|
16
|
-
garf_executors/entrypoints/cli/api.py
|
|
17
|
-
garf_executors/entrypoints/cli/bq.py
|
|
18
|
-
garf_executors/entrypoints/cli/gaarf.py
|
|
19
|
-
garf_executors/entrypoints/cli/sql.py
|
|
16
|
+
garf_executors/entrypoints/cli.py
|
|
17
|
+
garf_executors/entrypoints/utils.py
|
|
@@ -4,7 +4,6 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "garf-executors"
|
|
7
|
-
version = "0.0.1"
|
|
8
7
|
dependencies = [
|
|
9
8
|
"garf-core",
|
|
10
9
|
"garf-io",
|
|
@@ -28,6 +27,10 @@ classifiers = [
|
|
|
28
27
|
"Operating System :: OS Independent",
|
|
29
28
|
"License :: OSI Approved :: Apache Software License",
|
|
30
29
|
]
|
|
30
|
+
dynamic = ["version"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.dynamic]
|
|
33
|
+
version = {attr = "garf_executors.__version__"}
|
|
31
34
|
|
|
32
35
|
[project.optional-dependencies]
|
|
33
36
|
bq=[
|
|
@@ -42,5 +45,4 @@ all = [
|
|
|
42
45
|
"garf-executors[bq,sql]"
|
|
43
46
|
]
|
|
44
47
|
[project.scripts]
|
|
45
|
-
garf
|
|
46
|
-
garf-bq-executor="garf_executors.entrypoints.cli.bq:main"
|
|
48
|
+
garf="garf_executors.entrypoints.cli:main"
|
garf_executors-0.0.1/PKG-INFO
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: garf-executors
|
|
3
|
-
Version: 0.0.1
|
|
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>
|
|
6
|
-
License: Apache 2.0
|
|
7
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
-
Requires-Python: >=3.8
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: garf-core
|
|
20
|
-
Requires-Dist: garf-io
|
|
21
|
-
Provides-Extra: bq
|
|
22
|
-
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
|
-
Requires-Dist: pandas; extra == "bq"
|
|
24
|
-
Provides-Extra: sql
|
|
25
|
-
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
|
-
Requires-Dist: pandas; extra == "sql"
|
|
27
|
-
Provides-Extra: all
|
|
28
|
-
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
29
|
-
|
|
30
|
-
# Gaarf Executors
|
garf_executors-0.0.1/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Gaarf Executors
|
|
File without changes
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# Copyright 2022 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
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
-
# See the License for the specific language governing permissions and
|
|
12
|
-
# limitations under the License.
|
|
13
|
-
"""Module for defing `garf` CLI utility.
|
|
14
|
-
|
|
15
|
-
`garf` allows to execute GAQL queries and store results in local/remote
|
|
16
|
-
storage.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import functools
|
|
23
|
-
import sys
|
|
24
|
-
from collections.abc import MutableSequence
|
|
25
|
-
from concurrent import futures
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
|
|
28
|
-
import smart_open
|
|
29
|
-
import yaml
|
|
30
|
-
from garf import api_clients, exceptions, query_executor
|
|
31
|
-
from garf.cli import utils
|
|
32
|
-
from garf.io import reader, writer
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def main():
|
|
36
|
-
parser = argparse.ArgumentParser()
|
|
37
|
-
parser.add_argument('query', nargs='*')
|
|
38
|
-
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
39
|
-
parser.add_argument('--account', dest='account', default=None)
|
|
40
|
-
parser.add_argument('--output', dest='output', default=None)
|
|
41
|
-
parser.add_argument('--input', dest='input', default='file')
|
|
42
|
-
parser.add_argument(
|
|
43
|
-
'--ads-config', dest='config', default=str(Path.home() / 'google-ads.yaml')
|
|
44
|
-
)
|
|
45
|
-
parser.add_argument('--api-version', dest='api_version', default=None)
|
|
46
|
-
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
47
|
-
parser.add_argument('--logger', dest='logger', default='local')
|
|
48
|
-
parser.add_argument(
|
|
49
|
-
'--customer-ids-query', dest='customer_ids_query', default=None
|
|
50
|
-
)
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
'--customer-ids-query-file', dest='customer_ids_query_file', default=None
|
|
53
|
-
)
|
|
54
|
-
parser.add_argument('--save-config', dest='save_config', action='store_true')
|
|
55
|
-
parser.add_argument(
|
|
56
|
-
'--no-save-config', dest='save_config', action='store_false'
|
|
57
|
-
)
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
'--config-destination', dest='save_config_dest', default='config.yaml'
|
|
60
|
-
)
|
|
61
|
-
parser.add_argument(
|
|
62
|
-
'--parallel-queries', dest='parallel_queries', action='store_true'
|
|
63
|
-
)
|
|
64
|
-
parser.add_argument(
|
|
65
|
-
'--no-parallel-queries', dest='parallel_queries', action='store_false'
|
|
66
|
-
)
|
|
67
|
-
parser.add_argument(
|
|
68
|
-
'--optimize-performance', dest='optimize_performance', default='NONE'
|
|
69
|
-
)
|
|
70
|
-
parser.add_argument('--dry-run', dest='dry_run', action='store_true')
|
|
71
|
-
parser.add_argument(
|
|
72
|
-
'--disable-account-expansion',
|
|
73
|
-
dest='disable_account_expansion',
|
|
74
|
-
action='store_true',
|
|
75
|
-
)
|
|
76
|
-
parser.add_argument('-v', '--version', dest='version', action='store_true')
|
|
77
|
-
parser.add_argument(
|
|
78
|
-
'--parallel-threshold', dest='parallel_threshold', default=None, type=int
|
|
79
|
-
)
|
|
80
|
-
parser.set_defaults(save_config=False)
|
|
81
|
-
parser.set_defaults(parallel_queries=True)
|
|
82
|
-
parser.set_defaults(dry_run=False)
|
|
83
|
-
parser.set_defaults(disable_account_expansion=False)
|
|
84
|
-
args = parser.parse_known_args()
|
|
85
|
-
main_args = args[0]
|
|
86
|
-
|
|
87
|
-
if main_args.version:
|
|
88
|
-
import pkg_resources
|
|
89
|
-
|
|
90
|
-
version = pkg_resources.require('google-ads-api-report-fetcher')[0].version
|
|
91
|
-
print(f'garf version {version}')
|
|
92
|
-
sys.exit()
|
|
93
|
-
|
|
94
|
-
logger = utils.init_logging(
|
|
95
|
-
loglevel=main_args.loglevel.upper(), logger_type=main_args.logger
|
|
96
|
-
)
|
|
97
|
-
if not main_args.query:
|
|
98
|
-
logger.error('Please provide one or more queries to run')
|
|
99
|
-
raise exceptions.GarfMissingQueryException(
|
|
100
|
-
'Please provide one or more queries to run'
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
with smart_open.open(main_args.config, 'r', encoding='utf-8') as f:
|
|
104
|
-
google_ads_config_dict = yaml.safe_load(f)
|
|
105
|
-
|
|
106
|
-
config = utils.ConfigBuilder('garf').build(vars(main_args), args[1])
|
|
107
|
-
if not config.account:
|
|
108
|
-
if mcc := google_ads_config_dict.get('login_customer_id'):
|
|
109
|
-
config.account = str(mcc)
|
|
110
|
-
else:
|
|
111
|
-
raise exceptions.GarfMissingAccountException(
|
|
112
|
-
'No account found, please specify via --account CLI flag'
|
|
113
|
-
'or add as login_customer_id in google-ads.yaml'
|
|
114
|
-
)
|
|
115
|
-
logger.debug('config: %s', config)
|
|
116
|
-
|
|
117
|
-
if main_args.save_config and not main_args.garf_config:
|
|
118
|
-
utils.ConfigSaver(main_args.save_config_dest).save(config)
|
|
119
|
-
if main_args.dry_run:
|
|
120
|
-
sys.exit()
|
|
121
|
-
|
|
122
|
-
if config.params:
|
|
123
|
-
config = utils.initialize_runtime_parameters(config)
|
|
124
|
-
logger.debug('initialized config: %s', config)
|
|
125
|
-
|
|
126
|
-
ads_client = api_clients.GoogleAdsApiClient(
|
|
127
|
-
config_dict=google_ads_config_dict,
|
|
128
|
-
version=config.api_version,
|
|
129
|
-
use_proto_plus=main_args.optimize_performance
|
|
130
|
-
not in ('PROTOBUF', 'BATCH_PROTOBUF'),
|
|
131
|
-
)
|
|
132
|
-
ads_query_executor = query_executor.AdsQueryExecutor(ads_client)
|
|
133
|
-
reader_factory = reader.ReaderFactory()
|
|
134
|
-
reader_client = reader_factory.create_reader(main_args.input)
|
|
135
|
-
|
|
136
|
-
if config.customer_ids_query:
|
|
137
|
-
customer_ids_query = config.customer_ids_query
|
|
138
|
-
elif config.customer_ids_query_file:
|
|
139
|
-
file_reader = reader_factory.create_reader('file')
|
|
140
|
-
customer_ids_query = file_reader.read(config.customer_ids_query_file)
|
|
141
|
-
else:
|
|
142
|
-
customer_ids_query = None
|
|
143
|
-
|
|
144
|
-
if main_args.disable_account_expansion:
|
|
145
|
-
logger.info(
|
|
146
|
-
'Skipping account expansion because of ' 'disable_account_expansion flag'
|
|
147
|
-
)
|
|
148
|
-
customer_ids = (
|
|
149
|
-
config.account
|
|
150
|
-
if isinstance(config.account, MutableSequence)
|
|
151
|
-
else [config.account]
|
|
152
|
-
)
|
|
153
|
-
else:
|
|
154
|
-
customer_ids = ads_query_executor.expand_mcc(
|
|
155
|
-
config.account, customer_ids_query
|
|
156
|
-
)
|
|
157
|
-
if not customer_ids:
|
|
158
|
-
logger.warning(
|
|
159
|
-
'Not a single under MCC %s is found that satisfies '
|
|
160
|
-
'the following customer_id query: "%s"',
|
|
161
|
-
config.account,
|
|
162
|
-
customer_ids_query,
|
|
163
|
-
)
|
|
164
|
-
sys.exit()
|
|
165
|
-
writer_client = writer.WriterFactory().create_writer(
|
|
166
|
-
config.output, **config.writer_params
|
|
167
|
-
)
|
|
168
|
-
if config.output == 'bq':
|
|
169
|
-
_ = writer_client.create_or_get_dataset()
|
|
170
|
-
if config.output == 'sheet':
|
|
171
|
-
writer_client.init_client()
|
|
172
|
-
|
|
173
|
-
logger.info(
|
|
174
|
-
'Total number of customer_ids is %d, accounts=[%s]',
|
|
175
|
-
len(customer_ids),
|
|
176
|
-
','.join(map(str, customer_ids)),
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
if main_args.parallel_queries:
|
|
180
|
-
logger.info('Running queries in parallel')
|
|
181
|
-
with futures.ThreadPoolExecutor(main_args.parallel_threshold) as executor:
|
|
182
|
-
future_to_query = {
|
|
183
|
-
executor.submit(
|
|
184
|
-
ads_query_executor.execute,
|
|
185
|
-
reader_client.read(query),
|
|
186
|
-
query,
|
|
187
|
-
customer_ids,
|
|
188
|
-
writer_client,
|
|
189
|
-
config.params,
|
|
190
|
-
main_args.optimize_performance,
|
|
191
|
-
): query
|
|
192
|
-
for query in main_args.query
|
|
193
|
-
}
|
|
194
|
-
for future in futures.as_completed(future_to_query):
|
|
195
|
-
query = future_to_query[future]
|
|
196
|
-
utils.garf_runner(query, future.result, logger)
|
|
197
|
-
else:
|
|
198
|
-
logger.info('Running queries sequentially')
|
|
199
|
-
for query in main_args.query:
|
|
200
|
-
callback = functools.partial(
|
|
201
|
-
ads_query_executor.execute,
|
|
202
|
-
reader_client.read(query),
|
|
203
|
-
query,
|
|
204
|
-
customer_ids,
|
|
205
|
-
writer_client,
|
|
206
|
-
config.params,
|
|
207
|
-
main_args.optimize_performance,
|
|
208
|
-
)
|
|
209
|
-
utils.garf_runner(query, callback, logger)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if __name__ == '__main__':
|
|
213
|
-
main()
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Copyright 2022 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 defing `garf-bq` CLI utility.
|
|
15
|
-
|
|
16
|
-
`garf-bq` allows to execute BigQuery queries based on Garf config.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import functools
|
|
23
|
-
import sys
|
|
24
|
-
from concurrent import futures
|
|
25
|
-
|
|
26
|
-
from garf_writers import reader # type: ignore
|
|
27
|
-
|
|
28
|
-
from garf_executors import bq_executor
|
|
29
|
-
from garf_executors.entrypoints import utils
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def main():
|
|
33
|
-
parser = argparse.ArgumentParser()
|
|
34
|
-
parser.add_argument('query', nargs='+')
|
|
35
|
-
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
36
|
-
parser.add_argument('--project', dest='project')
|
|
37
|
-
parser.add_argument(
|
|
38
|
-
'--dataset-location', dest='dataset_location', default=None
|
|
39
|
-
)
|
|
40
|
-
parser.add_argument('--save-config', dest='save_config', action='store_true')
|
|
41
|
-
parser.add_argument(
|
|
42
|
-
'--no-save-config', dest='save_config', action='store_false'
|
|
43
|
-
)
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
'--config-destination', dest='save_config_dest', default='config.yaml'
|
|
46
|
-
)
|
|
47
|
-
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
48
|
-
parser.add_argument('--logger', dest='logger', default='local')
|
|
49
|
-
parser.add_argument('--dry-run', dest='dry_run', action='store_true')
|
|
50
|
-
parser.add_argument(
|
|
51
|
-
'--parallel-queries', dest='parallel_queries', action='store_true'
|
|
52
|
-
)
|
|
53
|
-
parser.add_argument(
|
|
54
|
-
'--no-parallel-queries', dest='parallel_queries', action='store_false'
|
|
55
|
-
)
|
|
56
|
-
parser.add_argument(
|
|
57
|
-
'--parallel-threshold', dest='parallel_threshold', default=None, type=int
|
|
58
|
-
)
|
|
59
|
-
parser.set_defaults(save_config=False)
|
|
60
|
-
parser.set_defaults(dry_run=False)
|
|
61
|
-
parser.set_defaults(parallel_queries=True)
|
|
62
|
-
args = parser.parse_known_args()
|
|
63
|
-
main_args = args[0]
|
|
64
|
-
|
|
65
|
-
logger = utils.init_logging(
|
|
66
|
-
loglevel=main_args.loglevel.upper(), logger_type=main_args.logger
|
|
67
|
-
)
|
|
68
|
-
config = utils.ConfigBuilder('garf-bq').build(vars(main_args), args[1])
|
|
69
|
-
logger.debug('config: %s', config)
|
|
70
|
-
if main_args.save_config and not main_args.garf_config:
|
|
71
|
-
utils.ConfigSaver(main_args.save_config_dest).save(config)
|
|
72
|
-
if main_args.dry_run:
|
|
73
|
-
sys.exit()
|
|
74
|
-
|
|
75
|
-
config = utils.initialize_runtime_parameters(config)
|
|
76
|
-
logger.debug('initialized config: %s', config)
|
|
77
|
-
|
|
78
|
-
bigquery_executor = bq_executor.BigQueryExecutor(
|
|
79
|
-
project_id=config.project, location=config.dataset_location
|
|
80
|
-
)
|
|
81
|
-
bigquery_executor.create_datasets(config.params.get('macro'))
|
|
82
|
-
|
|
83
|
-
reader_client = reader.FileReader()
|
|
84
|
-
|
|
85
|
-
if main_args.parallel_queries:
|
|
86
|
-
logger.info('Running queries in parallel')
|
|
87
|
-
with futures.ThreadPoolExecutor(
|
|
88
|
-
max_workers=main_args.parallel_threshold
|
|
89
|
-
) as executor:
|
|
90
|
-
future_to_query = {
|
|
91
|
-
executor.submit(
|
|
92
|
-
bigquery_executor.execute,
|
|
93
|
-
query,
|
|
94
|
-
reader_client.read(query),
|
|
95
|
-
config.params,
|
|
96
|
-
): query
|
|
97
|
-
for query in sorted(main_args.query)
|
|
98
|
-
}
|
|
99
|
-
for future in futures.as_completed(future_to_query):
|
|
100
|
-
query = future_to_query[future]
|
|
101
|
-
utils.postprocessor_runner(query, future.result, logger)
|
|
102
|
-
else:
|
|
103
|
-
logger.info('Running queries sequentially')
|
|
104
|
-
for query in sorted(main_args.query):
|
|
105
|
-
callback = functools.partial(
|
|
106
|
-
executor.execute, query, reader_client.read(query), config.params
|
|
107
|
-
)
|
|
108
|
-
utils.postprocessor_runner(query, callback, logger)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if __name__ == '__main__':
|
|
112
|
-
main()
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# Copyright 2022 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
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
-
# See the License for the specific language governing permissions and
|
|
12
|
-
# limitations under the License.
|
|
13
|
-
"""Module for defing `garf` CLI utility.
|
|
14
|
-
|
|
15
|
-
`garf` allows to execute GAQL queries and store results in local/remote
|
|
16
|
-
storage.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import functools
|
|
23
|
-
import sys
|
|
24
|
-
from collections.abc import MutableSequence
|
|
25
|
-
from concurrent import futures
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
|
|
28
|
-
import smart_open
|
|
29
|
-
import yaml
|
|
30
|
-
from garf import api_clients, exceptions, query_executor
|
|
31
|
-
from garf.cli import utils
|
|
32
|
-
from garf.io import reader, writer
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def main():
|
|
36
|
-
parser = argparse.ArgumentParser()
|
|
37
|
-
parser.add_argument('query', nargs='*')
|
|
38
|
-
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
39
|
-
parser.add_argument('--account', dest='account', default=None)
|
|
40
|
-
parser.add_argument('--output', dest='output', default=None)
|
|
41
|
-
parser.add_argument('--input', dest='input', default='file')
|
|
42
|
-
parser.add_argument(
|
|
43
|
-
'--ads-config', dest='config', default=str(Path.home() / 'google-ads.yaml')
|
|
44
|
-
)
|
|
45
|
-
parser.add_argument('--api-version', dest='api_version', default=None)
|
|
46
|
-
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
47
|
-
parser.add_argument('--logger', dest='logger', default='local')
|
|
48
|
-
parser.add_argument(
|
|
49
|
-
'--customer-ids-query', dest='customer_ids_query', default=None
|
|
50
|
-
)
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
'--customer-ids-query-file', dest='customer_ids_query_file', default=None
|
|
53
|
-
)
|
|
54
|
-
parser.add_argument('--save-config', dest='save_config', action='store_true')
|
|
55
|
-
parser.add_argument(
|
|
56
|
-
'--no-save-config', dest='save_config', action='store_false'
|
|
57
|
-
)
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
'--config-destination', dest='save_config_dest', default='config.yaml'
|
|
60
|
-
)
|
|
61
|
-
parser.add_argument(
|
|
62
|
-
'--parallel-queries', dest='parallel_queries', action='store_true'
|
|
63
|
-
)
|
|
64
|
-
parser.add_argument(
|
|
65
|
-
'--no-parallel-queries', dest='parallel_queries', action='store_false'
|
|
66
|
-
)
|
|
67
|
-
parser.add_argument(
|
|
68
|
-
'--optimize-performance', dest='optimize_performance', default='NONE'
|
|
69
|
-
)
|
|
70
|
-
parser.add_argument('--dry-run', dest='dry_run', action='store_true')
|
|
71
|
-
parser.add_argument(
|
|
72
|
-
'--disable-account-expansion',
|
|
73
|
-
dest='disable_account_expansion',
|
|
74
|
-
action='store_true',
|
|
75
|
-
)
|
|
76
|
-
parser.add_argument('-v', '--version', dest='version', action='store_true')
|
|
77
|
-
parser.add_argument(
|
|
78
|
-
'--parallel-threshold', dest='parallel_threshold', default=None, type=int
|
|
79
|
-
)
|
|
80
|
-
parser.set_defaults(save_config=False)
|
|
81
|
-
parser.set_defaults(parallel_queries=True)
|
|
82
|
-
parser.set_defaults(dry_run=False)
|
|
83
|
-
parser.set_defaults(disable_account_expansion=False)
|
|
84
|
-
args = parser.parse_known_args()
|
|
85
|
-
main_args = args[0]
|
|
86
|
-
|
|
87
|
-
if main_args.version:
|
|
88
|
-
import pkg_resources
|
|
89
|
-
|
|
90
|
-
version = pkg_resources.require('google-ads-api-report-fetcher')[0].version
|
|
91
|
-
print(f'garf version {version}')
|
|
92
|
-
sys.exit()
|
|
93
|
-
|
|
94
|
-
logger = utils.init_logging(
|
|
95
|
-
loglevel=main_args.loglevel.upper(), logger_type=main_args.logger
|
|
96
|
-
)
|
|
97
|
-
if not main_args.query:
|
|
98
|
-
logger.error('Please provide one or more queries to run')
|
|
99
|
-
raise exceptions.GarfMissingQueryException(
|
|
100
|
-
'Please provide one or more queries to run'
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
with smart_open.open(main_args.config, 'r', encoding='utf-8') as f:
|
|
104
|
-
google_ads_config_dict = yaml.safe_load(f)
|
|
105
|
-
|
|
106
|
-
config = utils.ConfigBuilder('garf').build(vars(main_args), args[1])
|
|
107
|
-
if not config.account:
|
|
108
|
-
if mcc := google_ads_config_dict.get('login_customer_id'):
|
|
109
|
-
config.account = str(mcc)
|
|
110
|
-
else:
|
|
111
|
-
raise exceptions.GarfMissingAccountException(
|
|
112
|
-
'No account found, please specify via --account CLI flag'
|
|
113
|
-
'or add as login_customer_id in google-ads.yaml'
|
|
114
|
-
)
|
|
115
|
-
logger.debug('config: %s', config)
|
|
116
|
-
|
|
117
|
-
if main_args.save_config and not main_args.garf_config:
|
|
118
|
-
utils.ConfigSaver(main_args.save_config_dest).save(config)
|
|
119
|
-
if main_args.dry_run:
|
|
120
|
-
sys.exit()
|
|
121
|
-
|
|
122
|
-
if config.params:
|
|
123
|
-
config = utils.initialize_runtime_parameters(config)
|
|
124
|
-
logger.debug('initialized config: %s', config)
|
|
125
|
-
|
|
126
|
-
ads_client = api_clients.GoogleAdsApiClient(
|
|
127
|
-
config_dict=google_ads_config_dict,
|
|
128
|
-
version=config.api_version,
|
|
129
|
-
use_proto_plus=main_args.optimize_performance
|
|
130
|
-
not in ('PROTOBUF', 'BATCH_PROTOBUF'),
|
|
131
|
-
)
|
|
132
|
-
ads_query_executor = query_executor.AdsQueryExecutor(ads_client)
|
|
133
|
-
reader_factory = reader.ReaderFactory()
|
|
134
|
-
reader_client = reader_factory.create_reader(main_args.input)
|
|
135
|
-
|
|
136
|
-
if config.customer_ids_query:
|
|
137
|
-
customer_ids_query = config.customer_ids_query
|
|
138
|
-
elif config.customer_ids_query_file:
|
|
139
|
-
file_reader = reader_factory.create_reader('file')
|
|
140
|
-
customer_ids_query = file_reader.read(config.customer_ids_query_file)
|
|
141
|
-
else:
|
|
142
|
-
customer_ids_query = None
|
|
143
|
-
|
|
144
|
-
if main_args.disable_account_expansion:
|
|
145
|
-
logger.info(
|
|
146
|
-
'Skipping account expansion because of ' 'disable_account_expansion flag'
|
|
147
|
-
)
|
|
148
|
-
customer_ids = (
|
|
149
|
-
config.account
|
|
150
|
-
if isinstance(config.account, MutableSequence)
|
|
151
|
-
else [config.account]
|
|
152
|
-
)
|
|
153
|
-
else:
|
|
154
|
-
customer_ids = ads_query_executor.expand_mcc(
|
|
155
|
-
config.account, customer_ids_query
|
|
156
|
-
)
|
|
157
|
-
if not customer_ids:
|
|
158
|
-
logger.warning(
|
|
159
|
-
'Not a single under MCC %s is found that satisfies '
|
|
160
|
-
'the following customer_id query: "%s"',
|
|
161
|
-
config.account,
|
|
162
|
-
customer_ids_query,
|
|
163
|
-
)
|
|
164
|
-
sys.exit()
|
|
165
|
-
writer_client = writer.WriterFactory().create_writer(
|
|
166
|
-
config.output, **config.writer_params
|
|
167
|
-
)
|
|
168
|
-
if config.output == 'bq':
|
|
169
|
-
_ = writer_client.create_or_get_dataset()
|
|
170
|
-
if config.output == 'sheet':
|
|
171
|
-
writer_client.init_client()
|
|
172
|
-
|
|
173
|
-
logger.info(
|
|
174
|
-
'Total number of customer_ids is %d, accounts=[%s]',
|
|
175
|
-
len(customer_ids),
|
|
176
|
-
','.join(map(str, customer_ids)),
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
if main_args.parallel_queries:
|
|
180
|
-
logger.info('Running queries in parallel')
|
|
181
|
-
with futures.ThreadPoolExecutor(main_args.parallel_threshold) as executor:
|
|
182
|
-
future_to_query = {
|
|
183
|
-
executor.submit(
|
|
184
|
-
ads_query_executor.execute,
|
|
185
|
-
reader_client.read(query),
|
|
186
|
-
query,
|
|
187
|
-
customer_ids,
|
|
188
|
-
writer_client,
|
|
189
|
-
config.params,
|
|
190
|
-
main_args.optimize_performance,
|
|
191
|
-
): query
|
|
192
|
-
for query in main_args.query
|
|
193
|
-
}
|
|
194
|
-
for future in futures.as_completed(future_to_query):
|
|
195
|
-
query = future_to_query[future]
|
|
196
|
-
utils.garf_runner(query, future.result, logger)
|
|
197
|
-
else:
|
|
198
|
-
logger.info('Running queries sequentially')
|
|
199
|
-
for query in main_args.query:
|
|
200
|
-
callback = functools.partial(
|
|
201
|
-
ads_query_executor.execute,
|
|
202
|
-
reader_client.read(query),
|
|
203
|
-
query,
|
|
204
|
-
customer_ids,
|
|
205
|
-
writer_client,
|
|
206
|
-
config.params,
|
|
207
|
-
main_args.optimize_performance,
|
|
208
|
-
)
|
|
209
|
-
utils.garf_runner(query, callback, logger)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if __name__ == '__main__':
|
|
213
|
-
main()
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Copyright 2023 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 defing `garf` CLI utility.
|
|
15
|
-
|
|
16
|
-
`garf-sql` allows to execute SQL queries in various Databases via SqlAlchemy.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import functools
|
|
23
|
-
import sys
|
|
24
|
-
from concurrent import futures
|
|
25
|
-
|
|
26
|
-
import sqlalchemy
|
|
27
|
-
from garf_writers import reader # type: ignore
|
|
28
|
-
|
|
29
|
-
from garf_executors import sql_executor
|
|
30
|
-
from garf_executors.entrypoints import utils
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def main():
|
|
34
|
-
parser = argparse.ArgumentParser()
|
|
35
|
-
parser.add_argument('query', nargs='+')
|
|
36
|
-
parser.add_argument('-c', '--config', dest='garf_config', default=None)
|
|
37
|
-
parser.add_argument('--conn', '--connection-string', dest='connection_string')
|
|
38
|
-
parser.add_argument('--save-config', dest='save_config', action='store_true')
|
|
39
|
-
parser.add_argument(
|
|
40
|
-
'--no-save-config', dest='save_config', action='store_false'
|
|
41
|
-
)
|
|
42
|
-
parser.add_argument(
|
|
43
|
-
'--config-destination', dest='save_config_dest', default='config.yaml'
|
|
44
|
-
)
|
|
45
|
-
parser.add_argument('--log', '--loglevel', dest='loglevel', default='info')
|
|
46
|
-
parser.add_argument('--logger', dest='logger', default='local')
|
|
47
|
-
parser.add_argument('--dry-run', dest='dry_run', action='store_true')
|
|
48
|
-
parser.add_argument(
|
|
49
|
-
'--parallel-queries', dest='parallel_queries', action='store_true'
|
|
50
|
-
)
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
'--no-parallel-queries', dest='parallel_queries', action='store_false'
|
|
53
|
-
)
|
|
54
|
-
parser.add_argument(
|
|
55
|
-
'--parallel-threshold', dest='parallel_threshold', default=None, type=int
|
|
56
|
-
)
|
|
57
|
-
parser.set_defaults(save_config=False)
|
|
58
|
-
parser.set_defaults(dry_run=False)
|
|
59
|
-
parser.set_defaults(parallel_queries=True)
|
|
60
|
-
args = parser.parse_known_args()
|
|
61
|
-
main_args = args[0]
|
|
62
|
-
|
|
63
|
-
logger = utils.init_logging(
|
|
64
|
-
loglevel=main_args.loglevel.upper(), logger_type=main_args.logger
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
config = utils.ConfigBuilder('garf-sql').build(vars(main_args), args[1])
|
|
68
|
-
logger.debug('config: %s', config)
|
|
69
|
-
if main_args.save_config and not main_args.garf_config:
|
|
70
|
-
utils.ConfigSaver(main_args.save_config_dest).save(config)
|
|
71
|
-
if main_args.dry_run:
|
|
72
|
-
sys.exit()
|
|
73
|
-
|
|
74
|
-
config = utils.initialize_runtime_parameters(config)
|
|
75
|
-
logger.debug('initialized config: %s', config)
|
|
76
|
-
|
|
77
|
-
engine = sqlalchemy.create_engine(config.connection_string)
|
|
78
|
-
sqlalchemy_query_executor = sql_executor.SqlAlchemyQueryExecutor(engine)
|
|
79
|
-
|
|
80
|
-
reader_client = reader.FileReader()
|
|
81
|
-
|
|
82
|
-
if main_args.parallel_queries:
|
|
83
|
-
logger.info('Running queries in parallel')
|
|
84
|
-
with futures.ThreadPoolExecutor(
|
|
85
|
-
max_workers=main_args.parallel_threshold
|
|
86
|
-
) as executor:
|
|
87
|
-
future_to_query = {
|
|
88
|
-
executor.submit(
|
|
89
|
-
sqlalchemy_query_executor.execute,
|
|
90
|
-
query,
|
|
91
|
-
reader_client.read(query),
|
|
92
|
-
config.params,
|
|
93
|
-
): query
|
|
94
|
-
for query in sorted(main_args.query)
|
|
95
|
-
}
|
|
96
|
-
for future in futures.as_completed(future_to_query):
|
|
97
|
-
query = future_to_query[future]
|
|
98
|
-
utils.postprocessor_runner(query, future.result, logger)
|
|
99
|
-
else:
|
|
100
|
-
logger.info('Running queries sequentially')
|
|
101
|
-
for query in sorted(main_args.query):
|
|
102
|
-
callback = functools.partial(
|
|
103
|
-
executor.execute, query, reader_client.read(query), config.params
|
|
104
|
-
)
|
|
105
|
-
utils.postprocessor_runner(query, callback, logger)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if __name__ == '__main__':
|
|
109
|
-
main()
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: garf-executors
|
|
3
|
-
Version: 0.0.1
|
|
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>
|
|
6
|
-
License: Apache 2.0
|
|
7
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
-
Requires-Python: >=3.8
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: garf-core
|
|
20
|
-
Requires-Dist: garf-io
|
|
21
|
-
Provides-Extra: bq
|
|
22
|
-
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
23
|
-
Requires-Dist: pandas; extra == "bq"
|
|
24
|
-
Provides-Extra: sql
|
|
25
|
-
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
26
|
-
Requires-Dist: pandas; extra == "sql"
|
|
27
|
-
Provides-Extra: all
|
|
28
|
-
Requires-Dist: garf-executors[bq,sql]; extra == "all"
|
|
29
|
-
|
|
30
|
-
# Gaarf Executors
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|