garf-executors 0.0.6__py3-none-any.whl → 0.1.4__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 +40 -10
- garf_executors/api_executor.py +28 -45
- garf_executors/bq_executor.py +56 -19
- garf_executors/config.py +51 -0
- garf_executors/entrypoints/cli.py +66 -49
- garf_executors/entrypoints/server.py +60 -17
- garf_executors/entrypoints/tracer.py +42 -0
- garf_executors/entrypoints/utils.py +32 -359
- garf_executors/execution_context.py +83 -0
- garf_executors/executor.py +87 -0
- garf_executors/fetchers.py +54 -16
- garf_executors/sql_executor.py +59 -22
- garf_executors/telemetry.py +20 -0
- {garf_executors-0.0.6.dist-info → garf_executors-0.1.4.dist-info}/METADATA +8 -3
- garf_executors-0.1.4.dist-info/RECORD +20 -0
- garf_executors-0.0.6.dist-info/RECORD +0 -15
- {garf_executors-0.0.6.dist-info → garf_executors-0.1.4.dist-info}/WHEEL +0 -0
- {garf_executors-0.0.6.dist-info → garf_executors-0.1.4.dist-info}/entry_points.txt +0 -0
- {garf_executors-0.0.6.dist-info → garf_executors-0.1.4.dist-info}/top_level.txt +0 -0
garf_executors/fetchers.py
CHANGED
|
@@ -13,25 +13,63 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import inspect
|
|
16
|
+
import sys
|
|
16
17
|
from importlib.metadata import entry_points
|
|
17
18
|
|
|
18
19
|
from garf_core import report_fetcher
|
|
20
|
+
from opentelemetry import trace
|
|
19
21
|
|
|
22
|
+
from garf_executors.telemetry import tracer
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
@tracer.start_as_current_span('find_fetchers')
|
|
26
|
+
def find_fetchers() -> set[str]:
|
|
27
|
+
"""Identifiers all available report fetchers."""
|
|
28
|
+
if entrypoints := _get_entrypoints('garf'):
|
|
29
|
+
return {fetcher.name for fetcher in entrypoints}
|
|
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:
|
|
25
69
|
try:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
except ModuleNotFoundError:
|
|
33
|
-
continue
|
|
34
|
-
return found_fetchers
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
FETCHERS = get_report_fetchers()
|
|
70
|
+
fetchers = entry_points()[group]
|
|
71
|
+
except KeyError:
|
|
72
|
+
fetchers = []
|
|
73
|
+
else:
|
|
74
|
+
fetchers = entry_points(group=group)
|
|
75
|
+
return fetchers
|
garf_executors/sql_executor.py
CHANGED
|
@@ -25,14 +25,24 @@ except ImportError as e:
|
|
|
25
25
|
|
|
26
26
|
import logging
|
|
27
27
|
import re
|
|
28
|
-
from typing import Any
|
|
29
28
|
|
|
30
29
|
import pandas as pd
|
|
30
|
+
from garf_core import query_editor, report
|
|
31
|
+
from opentelemetry import trace
|
|
31
32
|
|
|
32
|
-
from
|
|
33
|
+
from garf_executors import exceptions, execution_context, executor
|
|
34
|
+
from garf_executors.telemetry import tracer
|
|
33
35
|
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
34
37
|
|
|
35
|
-
|
|
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
|
+
):
|
|
36
46
|
"""Handles query execution via SqlAlchemy.
|
|
37
47
|
|
|
38
48
|
Attributes:
|
|
@@ -51,36 +61,63 @@ class SqlAlchemyQueryExecutor(query_editor.TemplateProcessorMixin):
|
|
|
51
61
|
def from_connection_string(
|
|
52
62
|
cls, connection_string: str
|
|
53
63
|
) -> SqlAlchemyQueryExecutor:
|
|
64
|
+
"""Creates executor from SqlAlchemy connection string.
|
|
65
|
+
|
|
66
|
+
https://docs.sqlalchemy.org/en/20/core/engines.html
|
|
67
|
+
"""
|
|
54
68
|
engine = sqlalchemy.create_engine(connection_string)
|
|
55
69
|
return cls(engine)
|
|
56
70
|
|
|
71
|
+
@tracer.start_as_current_span('sql.execute')
|
|
57
72
|
def execute(
|
|
58
73
|
self,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
query: str,
|
|
75
|
+
title: str,
|
|
76
|
+
context: execution_context.ExecutionContext = (
|
|
77
|
+
execution_context.ExecutionContext()
|
|
78
|
+
),
|
|
79
|
+
) -> report.GarfReport:
|
|
63
80
|
"""Executes query in a given database via SqlAlchemy.
|
|
64
81
|
|
|
65
82
|
Args:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
query: Location of the query.
|
|
84
|
+
title: Name of the query.
|
|
85
|
+
context: Query execution context.
|
|
69
86
|
|
|
70
87
|
Returns:
|
|
71
|
-
|
|
88
|
+
Report with data if query returns some data otherwise empty Report.
|
|
72
89
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
span = trace.get_current_span()
|
|
91
|
+
logging.info('Executing script: %s', title)
|
|
92
|
+
query_text = self.replace_params_template(query, context.query_parameters)
|
|
75
93
|
with self.engine.begin() as conn:
|
|
76
94
|
if re.findall(r'(create|update) ', query_text.lower()):
|
|
77
95
|
conn.connection.executescript(query_text)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
# pylint: disable=C0330, g-bad-import-order, g-multiple-import
|
|
16
|
+
from opentelemetry import trace
|
|
17
|
+
|
|
18
|
+
tracer = trace.get_tracer(
|
|
19
|
+
instrumenting_module_name='garf_executors',
|
|
20
|
+
)
|
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: garf-executors
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
7
7
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
9
8
|
Classifier: Programming Language :: Python :: 3.9
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
Requires-Dist: garf-core
|
|
21
21
|
Requires-Dist: garf-io
|
|
22
22
|
Requires-Dist: pyyaml
|
|
23
23
|
Requires-Dist: pydantic
|
|
24
|
+
Requires-Dist: opentelemetry-api
|
|
25
|
+
Requires-Dist: opentelemetry-sdk
|
|
24
26
|
Provides-Extra: bq
|
|
25
27
|
Requires-Dist: garf-io[bq]; extra == "bq"
|
|
26
28
|
Requires-Dist: pandas; extra == "bq"
|
|
29
|
+
Requires-Dist: google-cloud-logging; extra == "bq"
|
|
27
30
|
Provides-Extra: sql
|
|
28
31
|
Requires-Dist: garf-io[sqlalchemy]; extra == "sql"
|
|
29
32
|
Requires-Dist: pandas; extra == "sql"
|
|
30
33
|
Provides-Extra: server
|
|
31
34
|
Requires-Dist: fastapi[standard]; extra == "server"
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi; extra == "server"
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-otlp; extra == "server"
|
|
32
37
|
Provides-Extra: all
|
|
33
38
|
Requires-Dist: garf-executors[bq,server,sql]; extra == "all"
|
|
34
39
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
garf_executors/__init__.py,sha256=4BZv9zb3tjlpF4kQSdTj1L5IdR-BiNQwtejg5dPTTcY,1933
|
|
2
|
+
garf_executors/api_executor.py,sha256=TxHtdnXjXjfBDU0z13yCulqF0XcEqAoOdVeGczdTSXs,3590
|
|
3
|
+
garf_executors/bq_executor.py,sha256=LOKNitigaMk4U-UjBZTHy4vG092nw6suEbgo2rrHCTI,5002
|
|
4
|
+
garf_executors/config.py,sha256=TqCzijm1PRvL4p-9Zl-kPkcC1SFKjhgTfKMJFmJW3fQ,1688
|
|
5
|
+
garf_executors/exceptions.py,sha256=U_7Q2ZMOUf89gzZd2pw7y3g7i1NeByPPKfpZ3q7p3ZU,662
|
|
6
|
+
garf_executors/execution_context.py,sha256=X4Wm_rE1mnnN2FuC_9bL05a8h8ko7qraeGY955ijNJc,2800
|
|
7
|
+
garf_executors/executor.py,sha256=_Nj6CKgyhzwFOxneODDhV1bvLjrMEvIu93W8YF9-sXo,2481
|
|
8
|
+
garf_executors/fetchers.py,sha256=HQqnMb0wlasVfXmAA7cnsd73POXPEGPxaC5mpEOnQk4,2443
|
|
9
|
+
garf_executors/sql_executor.py,sha256=_4oVPZKTd3lrDE0SM6uQ_bl13Ay9uhQuD-PHO9247WM,3920
|
|
10
|
+
garf_executors/telemetry.py,sha256=P75klGEoYgJ_-pR-izUIQ7B88ufskQ4vmW1rETg63Nc,747
|
|
11
|
+
garf_executors/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
garf_executors/entrypoints/cli.py,sha256=Qbg10LLWHEMBjjsOfEMDZQtjWpUwh6WJKSnqiXOzF6A,4765
|
|
13
|
+
garf_executors/entrypoints/server.py,sha256=b9blyBvN774RiTHUCZkfE5kNVnrTaANrETI4WMDHJeQ,3255
|
|
14
|
+
garf_executors/entrypoints/tracer.py,sha256=A_nolmGuMT3wOZJsoPORjfdtPO2lXdbr6CZt5BW0RTY,1374
|
|
15
|
+
garf_executors/entrypoints/utils.py,sha256=5XiGR2IOxdzAOY0lEWUeUV7tIpKBGRnQaIwBYvzQB7c,4337
|
|
16
|
+
garf_executors-0.1.4.dist-info/METADATA,sha256=3Z0plyqxqwCKYOm2PlXIfvxGo0lAVkdIaLD0s0pgZzQ,2900
|
|
17
|
+
garf_executors-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
garf_executors-0.1.4.dist-info/entry_points.txt,sha256=LskWNFIw8j0WJuI18-32OZrlASXAMg1XtrRYwsKBz2E,61
|
|
19
|
+
garf_executors-0.1.4.dist-info/top_level.txt,sha256=sP4dCXOENPn1hDFAunjMV8Js4NND_KGeO_gQWuaT0EY,15
|
|
20
|
+
garf_executors-0.1.4.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
garf_executors/__init__.py,sha256=bcb29OEvsx2XNTpbUW0LvKxoYJt5BSX3S2gqQLdRIqU,955
|
|
2
|
-
garf_executors/api_executor.py,sha256=udrlMiYUmKh5NsIuJkNowqCenvtf5O925FPFawXSXbM,4021
|
|
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=6tpsd1Ive5igAlQuhCSkli-tZHp58uWAU86JWGvdVpE,2722
|
|
7
|
-
garf_executors/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
garf_executors/entrypoints/cli.py,sha256=mWvPQkaqarDj5byHRvNAweVbUQiHZLXrC-35zY7l4fs,4043
|
|
9
|
-
garf_executors/entrypoints/server.py,sha256=rJ29VKWKaYJci1BLxZx-0LSILmUMf5BK8G1RRjRS2ts,1836
|
|
10
|
-
garf_executors/entrypoints/utils.py,sha256=ZZJFe2N4KwgzPRvak9gW_B25qESnzOyuF-qYZ2wW2_M,14974
|
|
11
|
-
garf_executors-0.0.6.dist-info/METADATA,sha256=35dBABJ8cVH2nI0NonZ5VGO6W4IF0gtiiw-ZFZqZhgs,2648
|
|
12
|
-
garf_executors-0.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
garf_executors-0.0.6.dist-info/entry_points.txt,sha256=LskWNFIw8j0WJuI18-32OZrlASXAMg1XtrRYwsKBz2E,61
|
|
14
|
-
garf_executors-0.0.6.dist-info/top_level.txt,sha256=sP4dCXOENPn1hDFAunjMV8Js4NND_KGeO_gQWuaT0EY,15
|
|
15
|
-
garf_executors-0.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|