airless-google-cloud-core 0.0.1.dev1__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.
- airless/google/cloud/__init__.py +0 -0
- airless/google/cloud/core/__init__.py +0 -0
- airless/google/cloud/core/operator/__init__.py +0 -0
- airless/google/cloud/core/operator/base.py +18 -0
- airless/google/cloud/core/operator/delay.py +20 -0
- airless/google/cloud/core/operator/error.py +95 -0
- airless/google/cloud/core/operator/redirect.py +22 -0
- airless/google/cloud/pubsub/__init__.py +0 -0
- airless/google/cloud/pubsub/hook/__init__.py +0 -0
- airless/google/cloud/pubsub/hook/pubsub.py +30 -0
- airless_google_cloud_core-0.0.1.dev1.dist-info/METADATA +16 -0
- airless_google_cloud_core-0.0.1.dev1.dist-info/RECORD +14 -0
- airless_google_cloud_core-0.0.1.dev1.dist-info/WHEEL +5 -0
- airless_google_cloud_core-0.0.1.dev1.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from airless.core.operator.base import BaseFileOperator, BaseEventOperator
|
|
3
|
+
|
|
4
|
+
from airless.google.cloud.pubsub.hook import GooglePubsubHook
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseGoogleFileOperator(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 BaseGoogleEventOperator(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,20 @@
|
|
|
1
|
+
|
|
2
|
+
from time import sleep
|
|
3
|
+
|
|
4
|
+
from airless.core.operator.delay import DelayOperator
|
|
5
|
+
from airless.google.cloud.core.base import BaseGoogleEventOperator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GoogleDelayOperator(BaseGoogleEventOperator, DelayOperator):
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Operator that adds a delay to the pipeline. The maximum delay is 500 due
|
|
13
|
+
to Cloud Functions time constraint of 540 of execution time
|
|
14
|
+
|
|
15
|
+
It can receive 1 parameter:
|
|
16
|
+
seconds: number of seconds to wait
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
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.base import BaseDto
|
|
9
|
+
from airless.google.cloud.core.base import BaseGoogleEventOperator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoogleErrorReprocessOperator(BaseGoogleEventOperator):
|
|
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.redirect import RedirectOperator
|
|
3
|
+
from airless.google.cloud.core.base import BaseGoogleEventOperator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GoogleRedirectOperator(BaseGoogleEventOperator, 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
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from google.cloud import pubsub_v1
|
|
5
|
+
|
|
6
|
+
from airless.core.hook.queue 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.dev1
|
|
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,14 @@
|
|
|
1
|
+
airless/google/cloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
airless/google/cloud/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
airless/google/cloud/core/operator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
airless/google/cloud/core/operator/base.py,sha256=FxHj0MgDV8jEBRSZVtUS3J7SeegS-7eaUZmvDDETRJc,537
|
|
5
|
+
airless/google/cloud/core/operator/delay.py,sha256=S_aG20JjgQ6NDjq85pyO5vv8XDn0NvP4PncvjzKpy6s,498
|
|
6
|
+
airless/google/cloud/core/operator/error.py,sha256=nf5I1lsRj9_3EHHwPauxZNzYNuyepjgYJZ7Co6wP4bU,3801
|
|
7
|
+
airless/google/cloud/core/operator/redirect.py,sha256=ftGMaQJg1E4qx8pU_5bHDkY_hkbyJt4nCQB3iuyP_-E,757
|
|
8
|
+
airless/google/cloud/pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
airless/google/cloud/pubsub/hook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
airless/google/cloud/pubsub/hook/pubsub.py,sha256=r5b_aObasfnsiC8WfE5JtQ4xa1_LSDz2Mhy6Rc6YzxY,886
|
|
11
|
+
airless_google_cloud_core-0.0.1.dev1.dist-info/METADATA,sha256=rOGhmrhIjqveQQkwCdozGx8aRkR2CowR0QIwtuTKVXw,737
|
|
12
|
+
airless_google_cloud_core-0.0.1.dev1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
13
|
+
airless_google_cloud_core-0.0.1.dev1.dist-info/top_level.txt,sha256=vPLzRICE7VNGvU-zceoZ1N7H2TkerQXR475zjg_xPzU,8
|
|
14
|
+
airless_google_cloud_core-0.0.1.dev1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
airless
|