airless-google-cloud-core 0.0.1__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.
- airless_google_cloud_core-0.0.1/PKG-INFO +16 -0
- airless_google_cloud_core-0.0.1/README.md +5 -0
- airless_google_cloud_core-0.0.1/airless/__init__.py +1 -0
- airless_google_cloud_core-0.0.1/airless/google/__init__.py +0 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/__init__.py +0 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/__init__.py +0 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/operator/__init__.py +4 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/operator/base.py +18 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/operator/delay.py +18 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/operator/error.py +95 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/core/operator/redirect.py +22 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/pubsub/__init__.py +0 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/pubsub/hook/__init__.py +1 -0
- airless_google_cloud_core-0.0.1/airless/google/cloud/pubsub/hook/pubsub.py +30 -0
- airless_google_cloud_core-0.0.1/airless_google_cloud_core.egg-info/PKG-INFO +16 -0
- airless_google_cloud_core-0.0.1/airless_google_cloud_core.egg-info/SOURCES.txt +20 -0
- airless_google_cloud_core-0.0.1/airless_google_cloud_core.egg-info/dependency_links.txt +1 -0
- airless_google_cloud_core-0.0.1/airless_google_cloud_core.egg-info/requires.txt +2 -0
- airless_google_cloud_core-0.0.1/airless_google_cloud_core.egg-info/top_level.txt +1 -0
- airless_google_cloud_core-0.0.1/pyproject.toml +19 -0
- airless_google_cloud_core-0.0.1/setup.cfg +32 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: airless-google-cloud-core
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Airless package with the main components to use airless in google cloud
|
|
5
|
+
Project-URL: Homepage, https://github.com/astercapital/airless
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/astercapital/airless/issues
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: google-cloud-pubsub==2.13.11
|
|
10
|
+
Requires-Dist: airless-core>=0.1.0.dev1
|
|
11
|
+
|
|
12
|
+
# airless-google-cloud-core
|
|
13
|
+
|
|
14
|
+
[](https://badge.fury.io/py/airless-google-cloud-core)
|
|
15
|
+
|
|
16
|
+
airless-google-cloud-core [Airless](https://github.com/astercapital/airless) package with the main components to use airless in google cloud.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# airless-google-cloud-core
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/airless-google-cloud-core)
|
|
4
|
+
|
|
5
|
+
airless-google-cloud-core [Airless](https://github.com/astercapital/airless) package with the main components to use airless in google cloud.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from airless.core.operator import BaseFileOperator, BaseEventOperator
|
|
3
|
+
|
|
4
|
+
from airless.google.cloud.pubsub.hook import GooglePubsubHook
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GoogleBaseFileOperator(BaseFileOperator):
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.queue_hook = GooglePubsubHook() # Have to redefine this attribute for each vendor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GoogleBaseEventOperator(BaseEventOperator):
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.queue_hook = GooglePubsubHook() # Have to redefine this attribute for each vendor
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from airless.core.operator import DelayOperator
|
|
3
|
+
from airless.google.cloud.core.operator import GoogleBaseEventOperator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GoogleDelayOperator(GoogleBaseEventOperator, DelayOperator):
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Operator that adds a delay to the pipeline. The maximum delay is 500 due
|
|
11
|
+
to Cloud Functions time constraint of 540 of execution time
|
|
12
|
+
|
|
13
|
+
It can receive 1 parameter:
|
|
14
|
+
seconds: number of seconds to wait
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from airless.core.config import get_config
|
|
8
|
+
from airless.core.dto import BaseDto
|
|
9
|
+
from airless.google.cloud.core.operator import GoogleBaseEventOperator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoogleErrorReprocessOperator(GoogleBaseEventOperator):
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
def execute(self, data, topic):
|
|
18
|
+
|
|
19
|
+
input_type = data['input_type']
|
|
20
|
+
origin = data.get('origin', 'undefined')
|
|
21
|
+
message_id = data.get('event_id')
|
|
22
|
+
original_data = data['data']
|
|
23
|
+
metadata = original_data.get('metadata', {})
|
|
24
|
+
|
|
25
|
+
retry_interval = metadata.get('retry_interval', 5)
|
|
26
|
+
retries = metadata.get('retries', 0)
|
|
27
|
+
max_retries = metadata.get('max_retries', 2)
|
|
28
|
+
max_interval = metadata.get('max_interval', 480) # Cloud function max execution time is 540s (9 min), so set it to wait max 480s (8 min)
|
|
29
|
+
|
|
30
|
+
if (input_type == 'event') and (retries < max_retries):
|
|
31
|
+
time.sleep(min(retry_interval ** retries, max_interval))
|
|
32
|
+
original_data.setdefault('metadata', {})['retries'] = retries + 1
|
|
33
|
+
self.queue_hook.publish(
|
|
34
|
+
project=get_config('GCP_PROJECT'),
|
|
35
|
+
topic=origin,
|
|
36
|
+
data=original_data)
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
dto = BaseDto(
|
|
40
|
+
event_id=message_id,
|
|
41
|
+
resource=origin,
|
|
42
|
+
to_project=get_config('GCP_PROJECT'),
|
|
43
|
+
to_dataset=get_config('BIGQUERY_DATASET_ERROR'),
|
|
44
|
+
to_table=get_config('BIGQUERY_TABLE_ERROR'),
|
|
45
|
+
to_schema=None,
|
|
46
|
+
to_partition_column='_created_at',
|
|
47
|
+
to_extract_to_cols=False,
|
|
48
|
+
to_keys_format=None,
|
|
49
|
+
data=data)
|
|
50
|
+
self.queue_hook.publish(
|
|
51
|
+
project=get_config('GCP_PROJECT'),
|
|
52
|
+
topic=get_config('PUBSUB_TOPIC_PUBSUB_TO_BQ'),
|
|
53
|
+
data=dto.as_dict())
|
|
54
|
+
|
|
55
|
+
email_send_topic = get_config('PUBSUB_TOPIC_EMAIL_SEND', False)
|
|
56
|
+
if email_send_topic and (origin != email_send_topic):
|
|
57
|
+
self.notify_email(origin, message_id, data)
|
|
58
|
+
|
|
59
|
+
slack_send_topic = get_config('PUBSUB_TOPIC_SLACK_SEND', False)
|
|
60
|
+
if slack_send_topic and (origin != slack_send_topic):
|
|
61
|
+
self.notify_slack(origin, message_id, data)
|
|
62
|
+
|
|
63
|
+
def notify_email(self, origin, message_id, data):
|
|
64
|
+
email_message = {
|
|
65
|
+
'sender': get_config('EMAIL_SENDER_ERROR'),
|
|
66
|
+
'recipients': eval(get_config('EMAIL_RECIPIENTS_ERROR')),
|
|
67
|
+
'subject': f'{origin} | {message_id}',
|
|
68
|
+
'content': f'Input Type: {data["input_type"]} Origin: {origin}\nMessage ID: {message_id}\n\n {json.dumps(data["data"])}\n\n{data["error"]}'
|
|
69
|
+
}
|
|
70
|
+
self.queue_hook.publish(
|
|
71
|
+
project=get_config('GCP_PROJECT'),
|
|
72
|
+
topic=get_config('PUBSUB_TOPIC_EMAIL_SEND'),
|
|
73
|
+
data=email_message)
|
|
74
|
+
|
|
75
|
+
def notify_slack(self, origin, message_id, data):
|
|
76
|
+
slack_message = {
|
|
77
|
+
'channels': eval(get_config('SLACK_CHANNELS_ERROR')),
|
|
78
|
+
'message': f'{origin} | {message_id}\n\n{json.dumps(data["data"])}\n\n{data["error"]}'
|
|
79
|
+
}
|
|
80
|
+
self.queue_hook.publish(
|
|
81
|
+
project=get_config('GCP_PROJECT'),
|
|
82
|
+
topic=get_config('PUBSUB_TOPIC_SLACK_SEND'),
|
|
83
|
+
data=slack_message)
|
|
84
|
+
|
|
85
|
+
def prepare_row(self, row, message_id, origin):
|
|
86
|
+
return {
|
|
87
|
+
'_event_id': message_id or 1234,
|
|
88
|
+
'_resource': origin or 'local',
|
|
89
|
+
'_json': json.dumps(row),
|
|
90
|
+
'_created_at': str(datetime.now())
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def prepare_rows(self, data, message_id, origin):
|
|
94
|
+
prepared_rows = data if isinstance(data, list) else [data]
|
|
95
|
+
return [self.prepare_row(row, message_id, origin) for row in prepared_rows]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
from airless.core.operator import RedirectOperator
|
|
3
|
+
from airless.google.cloud.core.operator import GoogleBaseEventOperator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GoogleRedirectOperator(GoogleBaseEventOperator, RedirectOperator):
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Operator that receives one event from a pubsub topic
|
|
10
|
+
and publish multiple messages to another topic.
|
|
11
|
+
|
|
12
|
+
It can receive 4 parameters:
|
|
13
|
+
project: the project where the destination pubsub is hosted
|
|
14
|
+
topic: the pubsub topic it must publish the newly generated messages
|
|
15
|
+
messages: a list of messages to publish the topic
|
|
16
|
+
params: a list of dicts containing a key and a list of values
|
|
17
|
+
|
|
18
|
+
The output messages will be the product of messages and every param values list
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pubsub import (GooglePubsubHook)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from google.cloud import pubsub_v1
|
|
5
|
+
|
|
6
|
+
from airless.core.hook import QueueHook
|
|
7
|
+
from airless.core.config import get_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GooglePubsubHook(QueueHook):
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.publisher = pubsub_v1.PublisherClient()
|
|
15
|
+
|
|
16
|
+
def publish(self, project, topic, data):
|
|
17
|
+
|
|
18
|
+
if get_config('ENV') == 'prod':
|
|
19
|
+
topic_path = self.publisher.topic_path(project, topic)
|
|
20
|
+
|
|
21
|
+
message_bytes = json \
|
|
22
|
+
.dumps(data) \
|
|
23
|
+
.encode('utf-8')
|
|
24
|
+
|
|
25
|
+
publish_future = self.publisher.publish(topic_path, data=message_bytes)
|
|
26
|
+
publish_future.result(timeout=10)
|
|
27
|
+
self.logger.info(f'published to {project}.{topic}')
|
|
28
|
+
return 'Message published.'
|
|
29
|
+
else:
|
|
30
|
+
self.logger.debug(f'[DEV] Message published to Project {project}, Topic {topic}: {data}')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: airless-google-cloud-core
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Airless package with the main components to use airless in google cloud
|
|
5
|
+
Project-URL: Homepage, https://github.com/astercapital/airless
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/astercapital/airless/issues
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: google-cloud-pubsub==2.13.11
|
|
10
|
+
Requires-Dist: airless-core>=0.1.0.dev1
|
|
11
|
+
|
|
12
|
+
# airless-google-cloud-core
|
|
13
|
+
|
|
14
|
+
[](https://badge.fury.io/py/airless-google-cloud-core)
|
|
15
|
+
|
|
16
|
+
airless-google-cloud-core [Airless](https://github.com/astercapital/airless) package with the main components to use airless in google cloud.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.cfg
|
|
4
|
+
airless/__init__.py
|
|
5
|
+
airless/google/__init__.py
|
|
6
|
+
airless/google/cloud/__init__.py
|
|
7
|
+
airless/google/cloud/core/__init__.py
|
|
8
|
+
airless/google/cloud/core/operator/__init__.py
|
|
9
|
+
airless/google/cloud/core/operator/base.py
|
|
10
|
+
airless/google/cloud/core/operator/delay.py
|
|
11
|
+
airless/google/cloud/core/operator/error.py
|
|
12
|
+
airless/google/cloud/core/operator/redirect.py
|
|
13
|
+
airless/google/cloud/pubsub/__init__.py
|
|
14
|
+
airless/google/cloud/pubsub/hook/__init__.py
|
|
15
|
+
airless/google/cloud/pubsub/hook/pubsub.py
|
|
16
|
+
airless_google_cloud_core.egg-info/PKG-INFO
|
|
17
|
+
airless_google_cloud_core.egg-info/SOURCES.txt
|
|
18
|
+
airless_google_cloud_core.egg-info/dependency_links.txt
|
|
19
|
+
airless_google_cloud_core.egg-info/requires.txt
|
|
20
|
+
airless_google_cloud_core.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
airless
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "airless-google-cloud-core"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Airless package with the main components to use airless in google cloud"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
dynamic = ["dependencies", "requires-python"]
|
|
7
|
+
|
|
8
|
+
[build-system]
|
|
9
|
+
requires = ["setuptools", "wheel"]
|
|
10
|
+
build-backend = "setuptools.build_meta"
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
"Homepage" = "https://github.com/astercapital/airless"
|
|
14
|
+
"Bug Tracker" = "https://github.com/astercapital/airless/issues"
|
|
15
|
+
|
|
16
|
+
[tool.pytest.ini_options]
|
|
17
|
+
minversion = "6.0"
|
|
18
|
+
addopts = "--capture=sys --cache-clear --disable-warnings --junitxml=pytest.xml --cov-report=xml:cov.xml --cov-report=term-missing:skip-covered --cov=airless"
|
|
19
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = airless-google-cloud-core
|
|
3
|
+
version = 0.0.1
|
|
4
|
+
|
|
5
|
+
[options]
|
|
6
|
+
python_requires = >=3.9
|
|
7
|
+
packages = find_namespace:
|
|
8
|
+
install_requires =
|
|
9
|
+
google-cloud-pubsub==2.13.11
|
|
10
|
+
airless-core>=0.1.0.dev1
|
|
11
|
+
|
|
12
|
+
[options.packages.find]
|
|
13
|
+
include = airless*
|
|
14
|
+
|
|
15
|
+
[flake8]
|
|
16
|
+
exclude =
|
|
17
|
+
.git,
|
|
18
|
+
__pycache__,
|
|
19
|
+
build,
|
|
20
|
+
dist,
|
|
21
|
+
*.pyc,
|
|
22
|
+
*.egg-info,
|
|
23
|
+
*.egg,
|
|
24
|
+
temp
|
|
25
|
+
ignore = E501
|
|
26
|
+
max-line-length = 120
|
|
27
|
+
max-complexity = 15
|
|
28
|
+
|
|
29
|
+
[egg_info]
|
|
30
|
+
tag_build =
|
|
31
|
+
tag_date = 0
|
|
32
|
+
|