garf-executors 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of garf-executors might be problematic. Click here for more details.

@@ -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()
@@ -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.
@@ -0,0 +1,14 @@
1
+ garf_executors/__init__.py,sha256=wxioT11RaRswgv3nRKMyykRJ9uhAQPvZ5T0oLFGqyEo,955
2
+ garf_executors/api_executor.py,sha256=axB7msuZ7LXFk_f3MjIyGM2AhDILyu7g70zqNW0hG6Q,3149
3
+ garf_executors/bq_executor.py,sha256=JBPxbDRYgUgpJv6SqYiFPypTFjZGIZ-SOOb6dS2sZQY,3822
4
+ garf_executors/exceptions.py,sha256=U_7Q2ZMOUf89gzZd2pw7y3g7i1NeByPPKfpZ3q7p3ZU,662
5
+ garf_executors/fetchers.py,sha256=gkAKHsDPzJySg4wYLZeCmNINtk6f17-jFzOP7tE82r8,1226
6
+ garf_executors/sql_executor.py,sha256=vBNQ4HZZYxP_EYAh8Z4BerzLESfsNpXdhENzXIw-OIo,2532
7
+ garf_executors/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ garf_executors/entrypoints/cli.py,sha256=0132lMbHEOecxnZ3K8JoH8ImSJxwKHz40x3Z_yeIIFA,4339
9
+ garf_executors/entrypoints/utils.py,sha256=C-XbXunnt7HN27eHcTAO7iytu6rM4eWNKApxL345Z6g,15116
10
+ garf_executors-0.0.3.dist-info/METADATA,sha256=7tCyf4s6ZB60C5HALpaIpBfEByeCC_A3poDxFaZwx04,2424
11
+ garf_executors-0.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
12
+ garf_executors-0.0.3.dist-info/entry_points.txt,sha256=LskWNFIw8j0WJuI18-32OZrlASXAMg1XtrRYwsKBz2E,61
13
+ garf_executors-0.0.3.dist-info/top_level.txt,sha256=sP4dCXOENPn1hDFAunjMV8Js4NND_KGeO_gQWuaT0EY,15
14
+ garf_executors-0.0.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ garf = garf_executors.entrypoints.cli:main
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.2
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
@@ -1,16 +0,0 @@
1
- garf_executors/__init__.py,sha256=EWaHzmbB0XnTnYoR0bNsknBBbCet7tpv7gUwahtBIC0,873
2
- garf_executors/api_executor.py,sha256=axB7msuZ7LXFk_f3MjIyGM2AhDILyu7g70zqNW0hG6Q,3149
3
- garf_executors/bq_executor.py,sha256=JBPxbDRYgUgpJv6SqYiFPypTFjZGIZ-SOOb6dS2sZQY,3822
4
- garf_executors/sql_executor.py,sha256=vBNQ4HZZYxP_EYAh8Z4BerzLESfsNpXdhENzXIw-OIo,2532
5
- garf_executors/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- garf_executors/entrypoints/utils.py,sha256=C-XbXunnt7HN27eHcTAO7iytu6rM4eWNKApxL345Z6g,15116
7
- garf_executors/entrypoints/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- garf_executors/entrypoints/cli/api.py,sha256=0QzB7KdXyZnHhaMEGsFhj2B8hyNxV-F5JW6zC90rTcQ,7118
9
- garf_executors/entrypoints/cli/bq.py,sha256=Rk3nTkcGhyp1hnSSFsLFIFPXo33l_B_O1MiCK2fdZM8,3824
10
- garf_executors/entrypoints/cli/gaarf.py,sha256=0QzB7KdXyZnHhaMEGsFhj2B8hyNxV-F5JW6zC90rTcQ,7118
11
- garf_executors/entrypoints/cli/sql.py,sha256=tXQwhrLNUvfORxVdaZHnIawAR06oWZGro2vcreJ22Kc,3753
12
- garf_executors-0.0.2.dist-info/METADATA,sha256=rxCpgyIWOUo0tQu3HL4kM3cki8eW__tBPgFm3Ee3xIA,1175
13
- garf_executors-0.0.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
14
- garf_executors-0.0.2.dist-info/entry_points.txt,sha256=ksbFBDblKlOYqNyYoL3uaZVYWEYM_KWb0sWrvUamhd4,136
15
- garf_executors-0.0.2.dist-info/top_level.txt,sha256=sP4dCXOENPn1hDFAunjMV8Js4NND_KGeO_gQWuaT0EY,15
16
- garf_executors-0.0.2.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- garf-bq-executor = garf_executors.entrypoints.cli.bq:main
3
- garf-sql-executor = garf_executors.entrypoints.cli.sql:main