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.

Files changed (21) hide show
  1. {garf_executors-0.0.3 → garf_executors-0.0.6}/PKG-INFO +9 -4
  2. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/__init__.py +1 -1
  3. garf_executors-0.0.6/garf_executors/api_executor.py +132 -0
  4. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/cli.py +15 -27
  5. garf_executors-0.0.6/garf_executors/entrypoints/server.py +65 -0
  6. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/utils.py +20 -25
  7. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/sql_executor.py +7 -0
  8. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/PKG-INFO +9 -4
  9. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/SOURCES.txt +1 -0
  10. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/requires.txt +6 -1
  11. {garf_executors-0.0.3 → garf_executors-0.0.6}/pyproject.toml +8 -1
  12. garf_executors-0.0.3/garf_executors/api_executor.py +0 -98
  13. {garf_executors-0.0.3 → garf_executors-0.0.6}/README.md +0 -0
  14. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/bq_executor.py +0 -0
  15. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/entrypoints/__init__.py +0 -0
  16. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/exceptions.py +0 -0
  17. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors/fetchers.py +0 -0
  18. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/dependency_links.txt +0 -0
  19. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/entry_points.txt +0 -0
  20. {garf_executors-0.0.3 → garf_executors-0.0.6}/garf_executors.egg-info/top_level.txt +0 -0
  21. {garf_executors-0.0.3 → garf_executors-0.0.6}/setup.cfg +0 -0
@@ -1,8 +1,8 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: garf-executors
3
- Version: 0.0.3
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
 
@@ -27,4 +27,4 @@ __all__ = [
27
27
  'ApiQueryExecutor',
28
28
  ]
29
29
 
30
- __version__ = '0.0.3'
30
+ __version__ = '0.0.6'
@@ -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, writer
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=None)
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
- query_executor = garf_executors.api_executor.ApiQueryExecutor(
85
- concrete_api_fetcher()
86
- )
82
+ source_parameters = extra_parameters.get('source', {})
87
83
  reader_client = reader.create_reader(args.input)
88
84
 
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
-
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
- writer_client,
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
- query = future_to_query[future]
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
- 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)
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, Callable, TypedDict
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, key = key.split('.')
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", supported arguments {', '.join(self.identifiers)}"
311
+ f', supported arguments {", ".join(self.identifiers)}'
307
312
  )
308
313
  if provided_identifier != identifier:
309
314
  return None
310
- key = key.replace('-', '_')
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'--{identifier}.key=value is the correct format'
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.2
1
+ Metadata-Version: 2.4
2
2
  Name: garf-executors
3
- Version: 0.0.3
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
 
@@ -14,4 +14,5 @@ garf_executors.egg-info/requires.txt
14
14
  garf_executors.egg-info/top_level.txt
15
15
  garf_executors/entrypoints/__init__.py
16
16
  garf_executors/entrypoints/cli.py
17
+ garf_executors/entrypoints/server.py
17
18
  garf_executors/entrypoints/utils.py
@@ -1,13 +1,18 @@
1
1
  garf-core
2
2
  garf-io
3
+ pyyaml
4
+ pydantic
3
5
 
4
6
  [all]
5
- garf-executors[bq,sql]
7
+ garf-executors[bq,server,sql]
6
8
 
7
9
  [bq]
8
10
  garf-io[bq]
9
11
  pandas
10
12
 
13
+ [server]
14
+ fastapi[standard]
15
+
11
16
  [sql]
12
17
  garf-io[sqlalchemy]
13
18
  pandas
@@ -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