garf-executors 0.1.4__py3-none-any.whl → 1.0.2__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.
- garf/executors/__init__.py +60 -0
- garf/executors/api_executor.py +143 -0
- garf/executors/bq_executor.py +177 -0
- garf/executors/config.py +52 -0
- garf/executors/entrypoints/__init__.py +0 -0
- garf/executors/entrypoints/cli.py +177 -0
- garf/executors/entrypoints/grpc_server.py +67 -0
- garf/executors/entrypoints/server.py +117 -0
- garf/executors/entrypoints/tracer.py +57 -0
- garf/executors/entrypoints/utils.py +140 -0
- garf/executors/exceptions.py +17 -0
- garf/executors/execution_context.py +117 -0
- garf/executors/executor.py +124 -0
- garf/executors/fetchers.py +78 -0
- garf/executors/garf_pb2.py +45 -0
- garf/executors/garf_pb2_grpc.py +97 -0
- garf/executors/query_processor.py +61 -0
- garf/executors/sql_executor.py +142 -0
- garf/executors/telemetry.py +20 -0
- garf/executors/workflow.py +109 -0
- garf_executors/__init__.py +9 -44
- garf_executors/api_executor.py +9 -99
- garf_executors/bq_executor.py +9 -144
- garf_executors/config.py +9 -35
- garf_executors/entrypoints/__init__.py +25 -0
- garf_executors/entrypoints/cli.py +9 -116
- garf_executors/entrypoints/grcp_server.py +25 -0
- garf_executors/entrypoints/server.py +9 -92
- garf_executors/entrypoints/tracer.py +9 -26
- garf_executors/entrypoints/utils.py +9 -124
- garf_executors/exceptions.py +11 -3
- garf_executors/execution_context.py +9 -67
- garf_executors/executor.py +9 -71
- garf_executors/fetchers.py +9 -59
- garf_executors/sql_executor.py +9 -107
- garf_executors/telemetry.py +10 -5
- garf_executors/workflow.py +25 -0
- {garf_executors-0.1.4.dist-info → garf_executors-1.0.2.dist-info}/METADATA +17 -7
- garf_executors-1.0.2.dist-info/RECORD +42 -0
- garf_executors-1.0.2.dist-info/entry_points.txt +2 -0
- {garf_executors-0.1.4.dist-info → garf_executors-1.0.2.dist-info}/top_level.txt +1 -0
- garf_executors-0.1.4.dist-info/RECORD +0 -20
- garf_executors-0.1.4.dist-info/entry_points.txt +0 -2
- {garf_executors-0.1.4.dist-info → garf_executors-1.0.2.dist-info}/WHEEL +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,130 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
"""Module for various helpers for executing Garf as CLI tool."""
|
|
15
14
|
|
|
16
|
-
from __future__ import annotations
|
|
17
15
|
|
|
18
|
-
import
|
|
19
|
-
import logging
|
|
20
|
-
import sys
|
|
21
|
-
from collections.abc import Sequence
|
|
22
|
-
from typing import Any
|
|
16
|
+
import warnings
|
|
23
17
|
|
|
24
|
-
from
|
|
18
|
+
from garf.executors.entrypoints.utils import *
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
identifier: self._parse_params(identifier, params)
|
|
34
|
-
for identifier in self.identifiers
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
def _parse_params(self, identifier: str, params: Sequence[Any]) -> dict:
|
|
38
|
-
parsed_params = {}
|
|
39
|
-
if params:
|
|
40
|
-
raw_params = [param.split('=', maxsplit=1) for param in params]
|
|
41
|
-
for param in raw_params:
|
|
42
|
-
param_pair = self._identify_param_pair(identifier, param)
|
|
43
|
-
if param_pair:
|
|
44
|
-
parsed_params.update(param_pair)
|
|
45
|
-
return parsed_params
|
|
46
|
-
|
|
47
|
-
def _identify_param_pair(
|
|
48
|
-
self, identifier: str, param: Sequence[str]
|
|
49
|
-
) -> dict[str, Any] | None:
|
|
50
|
-
key = param[0]
|
|
51
|
-
if not identifier or identifier not in key:
|
|
52
|
-
return None
|
|
53
|
-
provided_identifier, *keys = key.split('.')
|
|
54
|
-
if not keys:
|
|
55
|
-
return None
|
|
56
|
-
if len(keys) > 1:
|
|
57
|
-
raise GarfParamsException(
|
|
58
|
-
f'{key} is invalid format,'
|
|
59
|
-
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
60
|
-
'are the correct formats'
|
|
61
|
-
)
|
|
62
|
-
provided_identifier = provided_identifier.replace('--', '')
|
|
63
|
-
if provided_identifier not in self.identifiers:
|
|
64
|
-
supported_arguments = ', '.join(self.identifiers)
|
|
65
|
-
raise GarfParamsException(
|
|
66
|
-
f'CLI argument {provided_identifier} is not supported'
|
|
67
|
-
f', supported arguments {supported_arguments}'
|
|
68
|
-
)
|
|
69
|
-
if provided_identifier != identifier:
|
|
70
|
-
return None
|
|
71
|
-
key = keys[0].replace('-', '_')
|
|
72
|
-
if not key:
|
|
73
|
-
raise GarfParamsException(
|
|
74
|
-
f'{identifier} {key} is invalid,'
|
|
75
|
-
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
76
|
-
'are the correct formats'
|
|
77
|
-
)
|
|
78
|
-
if len(param) == 2:
|
|
79
|
-
return {key: param[1]}
|
|
80
|
-
if len(param) == 1:
|
|
81
|
-
return {key: True}
|
|
82
|
-
raise GarfParamsException(
|
|
83
|
-
f'{identifier} {key} is invalid,'
|
|
84
|
-
f'`--{identifier}.key=value` or `--{identifier}.key` '
|
|
85
|
-
'are the correct formats'
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class GarfParamsException(Exception):
|
|
90
|
-
"""Defines exception for incorrect parameters."""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class LoggerEnum(str, enum.Enum):
|
|
94
|
-
local = 'local'
|
|
95
|
-
rich = 'rich'
|
|
96
|
-
gcloud = 'gcloud'
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def init_logging(
|
|
100
|
-
loglevel: str = 'INFO',
|
|
101
|
-
logger_type: str | LoggerEnum = 'local',
|
|
102
|
-
name: str = __name__,
|
|
103
|
-
) -> logging.Logger:
|
|
104
|
-
loglevel = getattr(logging, loglevel)
|
|
105
|
-
if logger_type == 'rich':
|
|
106
|
-
logging.basicConfig(
|
|
107
|
-
format='%(message)s',
|
|
108
|
-
level=loglevel,
|
|
109
|
-
datefmt='%Y-%m-%d %H:%M:%S',
|
|
110
|
-
handlers=[
|
|
111
|
-
rich_logging.RichHandler(rich_tracebacks=True),
|
|
112
|
-
],
|
|
113
|
-
)
|
|
114
|
-
elif logger_type == 'gcloud':
|
|
115
|
-
try:
|
|
116
|
-
import google.cloud.logging as glogging
|
|
117
|
-
except ImportError as e:
|
|
118
|
-
raise ImportError(
|
|
119
|
-
'Please install garf-executors with Cloud logging support - '
|
|
120
|
-
'`pip install garf-executors[bq]`'
|
|
121
|
-
) from e
|
|
122
|
-
|
|
123
|
-
client = glogging.Client()
|
|
124
|
-
handler = glogging.handlers.CloudLoggingHandler(client, name=name)
|
|
125
|
-
handler.close()
|
|
126
|
-
glogging.handlers.setup_logging(handler, log_level=loglevel)
|
|
127
|
-
logging.basicConfig(
|
|
128
|
-
level=loglevel,
|
|
129
|
-
handlers=[handler],
|
|
130
|
-
)
|
|
131
|
-
else:
|
|
132
|
-
logging.basicConfig(
|
|
133
|
-
format='[%(asctime)s][%(name)s][%(levelname)s] %(message)s',
|
|
134
|
-
stream=sys.stdout,
|
|
135
|
-
level=loglevel,
|
|
136
|
-
datefmt='%Y-%m-%d %H:%M:%S',
|
|
137
|
-
)
|
|
138
|
-
logging.getLogger('smart_open.smart_open_lib').setLevel(logging.WARNING)
|
|
139
|
-
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
|
140
|
-
return logging.getLogger(name)
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors.entrypoints' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors.entrypoints' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
garf_executors/exceptions.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -13,5 +13,13 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import warnings
|
|
17
|
+
|
|
18
|
+
from garf.executors.exceptions import *
|
|
19
|
+
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,72 +12,14 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
import warnings
|
|
18
17
|
|
|
19
|
-
from
|
|
18
|
+
from garf.executors.execution_context import *
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
from garf_core import query_editor
|
|
28
|
-
from garf_io import writer
|
|
29
|
-
from garf_io.writers import abs_writer
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class ExecutionContext(pydantic.BaseModel):
|
|
33
|
-
"""Common context for executing one or more queries.
|
|
34
|
-
|
|
35
|
-
Attributes:
|
|
36
|
-
query_parameters: Parameters to dynamically change query text.
|
|
37
|
-
fetcher_parameters: Parameters to specify fetching setup.
|
|
38
|
-
writer: Type of writer to use.
|
|
39
|
-
writer_parameters: Optional parameters to setup writer.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
query_parameters: query_editor.GarfQueryParameters | None = pydantic.Field(
|
|
43
|
-
default_factory=dict
|
|
44
|
-
)
|
|
45
|
-
fetcher_parameters: dict[str, str | bool | int | list[str | int]] | None = (
|
|
46
|
-
pydantic.Field(default_factory=dict)
|
|
47
|
-
)
|
|
48
|
-
writer: str | None = None
|
|
49
|
-
writer_parameters: dict[str, str] | None = pydantic.Field(
|
|
50
|
-
default_factory=dict
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
def model_post_init(self, __context__) -> None:
|
|
54
|
-
if self.fetcher_parameters is None:
|
|
55
|
-
self.fetcher_parameters = {}
|
|
56
|
-
if self.writer_parameters is None:
|
|
57
|
-
self.writer_parameters = {}
|
|
58
|
-
if not self.query_parameters:
|
|
59
|
-
self.query_parameters = query_editor.GarfQueryParameters()
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def from_file(
|
|
63
|
-
cls, path: str | pathlib.Path | os.PathLike[str]
|
|
64
|
-
) -> ExecutionContext:
|
|
65
|
-
"""Builds context from local or remote yaml file."""
|
|
66
|
-
with smart_open.open(path, 'r', encoding='utf-8') as f:
|
|
67
|
-
data = yaml.safe_load(f)
|
|
68
|
-
return ExecutionContext(**data)
|
|
69
|
-
|
|
70
|
-
def save(self, path: str | pathlib.Path | os.PathLike[str]) -> str:
|
|
71
|
-
"""Saves context to local or remote yaml file."""
|
|
72
|
-
with smart_open.open(path, 'w', encoding='utf-8') as f:
|
|
73
|
-
yaml.dump(self.model_dump(), f, encoding='utf-8')
|
|
74
|
-
return f'ExecutionContext is saved to {str(path)}'
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def writer_client(self) -> abs_writer.AbsWriter:
|
|
78
|
-
writer_client = writer.create_writer(self.writer, **self.writer_parameters)
|
|
79
|
-
if self.writer == 'bq':
|
|
80
|
-
_ = writer_client.create_or_get_dataset()
|
|
81
|
-
if self.writer == 'sheet':
|
|
82
|
-
writer_client.init_client()
|
|
83
|
-
return writer_client
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
garf_executors/executor.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,76 +12,14 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""Defines common functionality between executors."""
|
|
16
15
|
|
|
17
|
-
import
|
|
16
|
+
import warnings
|
|
18
17
|
|
|
19
|
-
from
|
|
18
|
+
from garf.executors.executor import *
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@tracer.start_as_current_span('api.execute_batch')
|
|
29
|
-
def execute_batch(
|
|
30
|
-
self,
|
|
31
|
-
batch: dict[str, str],
|
|
32
|
-
context: execution_context.ExecutionContext,
|
|
33
|
-
parallel_threshold: int = 10,
|
|
34
|
-
) -> list[str]:
|
|
35
|
-
"""Executes batch of queries for a common context.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
batch: Mapping between query_title and its text.
|
|
39
|
-
context: Execution context.
|
|
40
|
-
parallel_threshold: Number of queries to execute in parallel.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
Results of execution.
|
|
44
|
-
"""
|
|
45
|
-
span = trace.get_current_span()
|
|
46
|
-
span.set_attribute('api.parallel_threshold', parallel_threshold)
|
|
47
|
-
return asyncio.run(
|
|
48
|
-
self._run(
|
|
49
|
-
batch=batch, context=context, parallel_threshold=parallel_threshold
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
async def aexecute(
|
|
54
|
-
self,
|
|
55
|
-
query: str,
|
|
56
|
-
title: str,
|
|
57
|
-
context: execution_context.ExecutionContext,
|
|
58
|
-
) -> str:
|
|
59
|
-
"""Performs query execution asynchronously.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
query: Location of the query.
|
|
63
|
-
title: Name of the query.
|
|
64
|
-
context: Query execution context.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
Result of writing the report.
|
|
68
|
-
"""
|
|
69
|
-
return await asyncio.to_thread(self.execute, query, title, context)
|
|
70
|
-
|
|
71
|
-
async def _run(
|
|
72
|
-
self,
|
|
73
|
-
batch: dict[str, str],
|
|
74
|
-
context: execution_context.ExecutionContext,
|
|
75
|
-
parallel_threshold: int,
|
|
76
|
-
):
|
|
77
|
-
semaphore = asyncio.Semaphore(value=parallel_threshold)
|
|
78
|
-
|
|
79
|
-
async def run_with_semaphore(fn):
|
|
80
|
-
async with semaphore:
|
|
81
|
-
return await fn
|
|
82
|
-
|
|
83
|
-
tasks = [
|
|
84
|
-
self.aexecute(query=query, title=title, context=context)
|
|
85
|
-
for title, query in batch.items()
|
|
86
|
-
]
|
|
87
|
-
return await asyncio.gather(*(run_with_semaphore(task) for task in tasks))
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
garf_executors/fetchers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,64 +12,14 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import inspect
|
|
16
|
-
import sys
|
|
17
|
-
from importlib.metadata import entry_points
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
from opentelemetry import trace
|
|
16
|
+
import warnings
|
|
21
17
|
|
|
22
|
-
from
|
|
18
|
+
from garf.executors.fetchers import *
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return set()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@tracer.start_as_current_span('get_report_fetcher')
|
|
34
|
-
def get_report_fetcher(source: str) -> type[report_fetcher.ApiReportFetcher]:
|
|
35
|
-
"""Loads report fetcher for a given source.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
source: Alias for a source associated with a fetcher.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Class for a found report fetcher.
|
|
42
|
-
|
|
43
|
-
Raises:
|
|
44
|
-
ApiReportFetcherError: When fetcher cannot be loaded.
|
|
45
|
-
MissingApiReportFetcherError: When fetcher not found.
|
|
46
|
-
"""
|
|
47
|
-
if source not in find_fetchers():
|
|
48
|
-
raise report_fetcher.MissingApiReportFetcherError(source)
|
|
49
|
-
for fetcher in _get_entrypoints('garf'):
|
|
50
|
-
if fetcher.name == source:
|
|
51
|
-
try:
|
|
52
|
-
with tracer.start_as_current_span('load_fetcher_module') as span:
|
|
53
|
-
fetcher_module = fetcher.load()
|
|
54
|
-
span.set_attribute('loaded_module', fetcher_module.__name__)
|
|
55
|
-
for name, obj in inspect.getmembers(fetcher_module):
|
|
56
|
-
if inspect.isclass(obj) and issubclass(
|
|
57
|
-
obj, report_fetcher.ApiReportFetcher
|
|
58
|
-
):
|
|
59
|
-
return getattr(fetcher_module, name)
|
|
60
|
-
except ModuleNotFoundError:
|
|
61
|
-
continue
|
|
62
|
-
raise report_fetcher.ApiReportFetcherError(
|
|
63
|
-
f'No fetcher available for the source "{source}"'
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _get_entrypoints(group='garf'):
|
|
68
|
-
if sys.version_info.major == 3 and sys.version_info.minor == 9:
|
|
69
|
-
try:
|
|
70
|
-
fetchers = entry_points()[group]
|
|
71
|
-
except KeyError:
|
|
72
|
-
fetchers = []
|
|
73
|
-
else:
|
|
74
|
-
fetchers = entry_points(group=group)
|
|
75
|
-
return fetchers
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
garf_executors/sql_executor.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,113 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
"""Defines mechanism for executing queries via SqlAlchemy."""
|
|
15
14
|
|
|
16
|
-
from __future__ import annotations
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
import sqlalchemy
|
|
20
|
-
except ImportError as e:
|
|
21
|
-
raise ImportError(
|
|
22
|
-
'Please install garf-executors with sqlalchemy support '
|
|
23
|
-
'- `pip install garf-executors[sqlalchemy]`'
|
|
24
|
-
) from e
|
|
16
|
+
import warnings
|
|
25
17
|
|
|
26
|
-
import
|
|
27
|
-
import re
|
|
18
|
+
from garf.executors.sql_executor import *
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
logger = logging.getLogger(__name__)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class SqlAlchemyQueryExecutorError(exceptions.GarfExecutorError):
|
|
40
|
-
"""Error when SqlAlchemyQueryExecutor fails to run query."""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class SqlAlchemyQueryExecutor(
|
|
44
|
-
executor.Executor, query_editor.TemplateProcessorMixin
|
|
45
|
-
):
|
|
46
|
-
"""Handles query execution via SqlAlchemy.
|
|
47
|
-
|
|
48
|
-
Attributes:
|
|
49
|
-
engine: Initialized Engine object to operated on a given database.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, engine: sqlalchemy.engine.base.Engine) -> None:
|
|
53
|
-
"""Initializes executor with a given engine.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
engine: Initialized Engine object to operated on a given database.
|
|
57
|
-
"""
|
|
58
|
-
self.engine = engine
|
|
59
|
-
|
|
60
|
-
@classmethod
|
|
61
|
-
def from_connection_string(
|
|
62
|
-
cls, connection_string: str
|
|
63
|
-
) -> SqlAlchemyQueryExecutor:
|
|
64
|
-
"""Creates executor from SqlAlchemy connection string.
|
|
65
|
-
|
|
66
|
-
https://docs.sqlalchemy.org/en/20/core/engines.html
|
|
67
|
-
"""
|
|
68
|
-
engine = sqlalchemy.create_engine(connection_string)
|
|
69
|
-
return cls(engine)
|
|
70
|
-
|
|
71
|
-
@tracer.start_as_current_span('sql.execute')
|
|
72
|
-
def execute(
|
|
73
|
-
self,
|
|
74
|
-
query: str,
|
|
75
|
-
title: str,
|
|
76
|
-
context: execution_context.ExecutionContext = (
|
|
77
|
-
execution_context.ExecutionContext()
|
|
78
|
-
),
|
|
79
|
-
) -> report.GarfReport:
|
|
80
|
-
"""Executes query in a given database via SqlAlchemy.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
query: Location of the query.
|
|
84
|
-
title: Name of the query.
|
|
85
|
-
context: Query execution context.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Report with data if query returns some data otherwise empty Report.
|
|
89
|
-
"""
|
|
90
|
-
span = trace.get_current_span()
|
|
91
|
-
logging.info('Executing script: %s', title)
|
|
92
|
-
query_text = self.replace_params_template(query, context.query_parameters)
|
|
93
|
-
with self.engine.begin() as conn:
|
|
94
|
-
if re.findall(r'(create|update) ', query_text.lower()):
|
|
95
|
-
conn.connection.executescript(query_text)
|
|
96
|
-
results = report.GarfReport()
|
|
97
|
-
else:
|
|
98
|
-
temp_table_name = f'temp_{title}'.replace('.', '_')
|
|
99
|
-
query_text = f'CREATE TABLE {temp_table_name} AS {query_text}'
|
|
100
|
-
conn.connection.executescript(query_text)
|
|
101
|
-
try:
|
|
102
|
-
results = report.GarfReport.from_pandas(
|
|
103
|
-
pd.read_sql(f'SELECT * FROM {temp_table_name}', conn)
|
|
104
|
-
)
|
|
105
|
-
finally:
|
|
106
|
-
conn.connection.execute(f'DROP TABLE {temp_table_name}')
|
|
107
|
-
if context.writer and results:
|
|
108
|
-
writer_client = context.writer_client
|
|
109
|
-
logger.debug(
|
|
110
|
-
'Start writing data for query %s via %s writer',
|
|
111
|
-
title,
|
|
112
|
-
type(writer_client),
|
|
113
|
-
)
|
|
114
|
-
writing_result = writer_client.write(results, title)
|
|
115
|
-
logger.debug(
|
|
116
|
-
'Finish writing data for query %s via %s writer',
|
|
117
|
-
title,
|
|
118
|
-
type(writer_client),
|
|
119
|
-
)
|
|
120
|
-
logger.info('%s executed successfully', title)
|
|
121
|
-
return writing_result
|
|
122
|
-
span.set_attribute('execute.num_results', len(results))
|
|
123
|
-
return results
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
garf_executors/telemetry.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
|
16
|
-
from opentelemetry import trace
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import warnings
|
|
17
|
+
|
|
18
|
+
from garf.executors.telemetry import *
|
|
19
|
+
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
20
25
|
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
import warnings
|
|
17
|
+
|
|
18
|
+
from garf.executors.workflow import *
|
|
19
|
+
|
|
20
|
+
warnings.warn(
|
|
21
|
+
"The 'garf_executors' namespace is deprecated. "
|
|
22
|
+
"Please use 'garf.executors' instead.",
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
stacklevel=2,
|
|
25
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: garf-executors
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Executes queries against API and writes data to local/remote storage.
|
|
5
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
|
|
@@ -17,25 +17,29 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
|
-
Requires-Dist: garf-core
|
|
21
|
-
Requires-Dist: garf-io
|
|
20
|
+
Requires-Dist: garf-core>=1.0.0
|
|
21
|
+
Requires-Dist: garf-io>=1.0.0
|
|
22
22
|
Requires-Dist: pyyaml
|
|
23
23
|
Requires-Dist: pydantic
|
|
24
24
|
Requires-Dist: opentelemetry-api
|
|
25
25
|
Requires-Dist: opentelemetry-sdk
|
|
26
|
+
Requires-Dist: opentelemetry-exporter-otlp
|
|
26
27
|
Provides-Extra: bq
|
|
27
28
|
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
28
29
|
Requires-Dist: pandas; extra == "bq"
|
|
29
30
|
Requires-Dist: google-cloud-logging; extra == "bq"
|
|
31
|
+
Requires-Dist: smart_open[gcs]; extra == "bq"
|
|
30
32
|
Provides-Extra: sql
|
|
31
33
|
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
32
34
|
Requires-Dist: pandas; extra == "sql"
|
|
35
|
+
Provides-Extra: gcp
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-gcp-trace; extra == "gcp"
|
|
33
37
|
Provides-Extra: server
|
|
34
38
|
Requires-Dist: fastapi[standard]; extra == "server"
|
|
35
39
|
Requires-Dist: opentelemetry-instrumentation-fastapi; extra == "server"
|
|
36
|
-
Requires-Dist:
|
|
40
|
+
Requires-Dist: typer; extra == "server"
|
|
37
41
|
Provides-Extra: all
|
|
38
|
-
Requires-Dist: garf-executors[bq,server,sql]; extra == "all"
|
|
42
|
+
Requires-Dist: garf-executors[bq,gcp,server,sql]; extra == "all"
|
|
39
43
|
|
|
40
44
|
# `garf-executors` - One stop-shop for interacting with Reporting APIs.
|
|
41
45
|
|
|
@@ -64,8 +68,14 @@ garf <QUERIES> --source <API_SOURCE> \
|
|
|
64
68
|
where
|
|
65
69
|
|
|
66
70
|
* `<QUERIES>`- local or remote path(s) to files with queries.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
71
|
+
* `source`- type of API to use. Based on that the appropriate report fetcher will be initialized. Explore supported APIs [here](https://google.github.io/garf/fetchers/overview/)
|
|
72
|
+
* `output` - output supported by [`garf-io` library](https://google.github.io/garf/usage/writers/).
|
|
69
73
|
|
|
70
74
|
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*.
|
|
71
75
|
> Concrete `--source` parameters are dependent on a particular report fetcher and should be looked up in a documentation for this fetcher.
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
Explore full documentation working with `garf-executors`
|
|
80
|
+
|
|
81
|
+
* [Documentation](https://google.github.io/garf/usage/executors/)
|