Compare commits

...

23 Commits

Author SHA1 Message Date
Matthias Bisping
d3605b0913 removed obsolete code 2022-03-14 19:09:50 +01:00
Matthias Bisping
9130b9fc75 test refac 2022-03-14 19:05:59 +01:00
Matthias Bisping
1205adecdb removed obsolete import 2022-03-14 14:49:59 +01:00
Matthias Bisping
59d0de97e3 applied black 2022-03-14 14:49:21 +01:00
Matthias Bisping
f78a6894bc applied black 2022-03-14 14:49:03 +01:00
Matthias Bisping
32740a914f removed obsolete code 2022-03-14 14:47:47 +01:00
Matthias Bisping
f21cbc141e consumer with visitor test fixed (added connection.close() in case of pika queue manager) 2022-03-14 14:47:27 +01:00
Matthias Bisping
c415dcd8e6 consumer with visitor test WIP 2 2022-03-14 13:56:54 +01:00
Matthias Bisping
ccad97992d consumer with visitor test WIP 2022-03-14 13:53:37 +01:00
Matthias Bisping
b4b060ac0e added compression / decompression; jsons put on storage rather than only result data 2022-03-11 11:43:27 +01:00
Matthias Bisping
66a27d26d3 fixed duplicate function name 2022-03-11 10:24:51 +01:00
Matthias Bisping
4f9c5626b5 file extensions are now assumed to be part of the request 2022-03-11 10:23:14 +01:00
Julius Unverfehrt
91980ff3ed add retry for prometheus endpoint metric getter 2022-03-11 10:09:39 +01:00
Matthias Bisping
963132afb1 added test for side-effect of storage response strategy 2022-03-10 16:41:53 +01:00
Matthias Bisping
896918570e added response strategies to queue visitor 2022-03-10 16:09:40 +01:00
Matthias Bisping
f0ffc8fb8b added tests for body processing with visitor 2022-03-10 15:10:34 +01:00
Matthias Bisping
8d6dd9e552 added tests for queue visitor 2022-03-10 14:43:19 +01:00
Matthias Bisping
eadefc9b14 pytest automation for testing all queue managers 2022-03-10 13:28:22 +01:00
Matthias Bisping
1347fb57f8 test refac 2022-03-10 13:05:42 +01:00
Matthias Bisping
ce1217c191 adapted queue manager mock for new queue manager interface 2022-03-10 13:02:59 +01:00
Matthias Bisping
9aff168f0d renaming 2022-03-10 13:02:28 +01:00
Julius Unverfehrt
08f2746afc Pull request #19: Prometheus tunneling
Merge in RR/pyinfra from prometheus-tunneling to pika_encapsulation

Squashed commit of the following:

commit 350448ec0f14849844deaa1a86ba4397ab3ebf3c
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date:   Wed Mar 9 16:52:02 2022 +0100

    quickfix

commit ee88be4c80abdf597013c743ce2d490ad2b3e029
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date:   Wed Mar 9 15:26:19 2022 +0100

    added prometheus endpoint to tunnel metrics from analysis endpoint
2022-03-10 08:24:03 +01:00
Matthias Bisping
21592342d5 pika queue manager works now in basic version 2022-03-08 13:37:40 +01:00
20 changed files with 511 additions and 10 deletions

View File

@ -2,8 +2,8 @@ service:
logging_level: $LOGGING_LEVEL_ROOT|DEBUG # Logging level for service logger logging_level: $LOGGING_LEVEL_ROOT|DEBUG # Logging level for service logger
response: response:
type: $RESPONSE_TYPE|"file" # Whether the analysis response is stored as file on storage or sent as stream type: $RESPONSE_TYPE|"file" # Whether the analysis response is stored as file on storage or sent as stream
extension: $RESPONSE_FILE_EXTENSION|".IMAGE_INFO.json.gz" # {.IMAGE_INFO.json.gz | .NER_ENTITIES.json.gz} # extension: $RESPONSE_FILE_EXTENSION|"IMAGE_INFO.json.gz" # {.IMAGE_INFO.json.gz | .NER_ENTITIES.json.gz}
key: $RESPONSE_KEY|"imageMetadata" # the key of the result {result, imageMetadata} # key: $RESPONSE_KEY|"imageMetadata" # the key of the result {result, imageMetadata}
probing_webserver: probing_webserver:
host: $PROBING_WEBSERVER_HOST|"0.0.0.0" # Probe webserver address host: $PROBING_WEBSERVER_HOST|"0.0.0.0" # Probe webserver address
@ -37,7 +37,7 @@ storage:
backend: $STORAGE_BACKEND|s3 # The type of storage to use {s3, azure} backend: $STORAGE_BACKEND|s3 # The type of storage to use {s3, azure}
bucket: $STORAGE_BUCKET|"pyinfra-test-bucket" # The bucket / container to pull files specified in queue requests from bucket: $STORAGE_BUCKET|"pyinfra-test-bucket" # The bucket / container to pull files specified in queue requests from
target_file_extension: $TARGET_FILE_EXTENSION|".ORIGIN.pdf.gz" # {.TEXT.json.gz | .ORIGIN.pdf.gz} Defines type of file to pull from storage # TODO: Caller should specify exact file name, including extension!
s3: s3:
endpoint: $STORAGE_ENDPOINT|"http://127.0.0.1:9000" endpoint: $STORAGE_ENDPOINT|"http://127.0.0.1:9000"

View File

@ -1,11 +1,23 @@
"""Implements a config object with dot-indexing syntax.""" """Implements a config object with dot-indexing syntax."""
from envyaml import EnvYAML from envyaml import EnvYAML
from pyinfra.locations import CONFIG_FILE from pyinfra.locations import CONFIG_FILE
def make_art():
return """
______ _____ __
| ___ \ |_ _| / _|
| |_/ / _ | | _ __ | |_ _ __ __ _
| __/ | | || || '_ \| _| '__/ _` |
| | | |_| || || | | | | | | | (_| |
\_| \__, \___/_| |_|_| |_| \__,_|
__/ |
|___/
"""
def _get_item_and_maybe_make_dotindexable(container, item): def _get_item_and_maybe_make_dotindexable(container, item):
ret = container[item] ret = container[item]
return DotIndexable(ret) if isinstance(ret, dict) else ret return DotIndexable(ret) if isinstance(ret, dict) else ret

View File

@ -1,11 +1,12 @@
import requests
from flask import Flask, jsonify from flask import Flask, jsonify
from retry import retry
from waitress import serve from waitress import serve
from pyinfra.config import CONFIG from pyinfra.config import CONFIG
def run_probing_webserver(app, host=None, port=None, mode=None): def run_probing_webserver(app, host=None, port=None, mode=None):
if not host: if not host:
host = CONFIG.probing_webserver.host host = CONFIG.probing_webserver.host
@ -23,7 +24,6 @@ def run_probing_webserver(app, host=None, port=None, mode=None):
def set_up_probing_webserver(): def set_up_probing_webserver():
app = Flask(__name__) app = Flask(__name__)
@app.route("/ready", methods=["GET"]) @app.route("/ready", methods=["GET"])
@ -38,4 +38,15 @@ def set_up_probing_webserver():
resp.status_code = 200 resp.status_code = 200
return resp return resp
@app.route("/prometheus", methods=["GET"])
def get_analysis_prometheus_endpoint():
@retry(requests.exceptions.ConnectionError, tries=3, delay=5, jitter=(1, 3))
def inner():
prom_endpoint = f"{CONFIG.rabbitmq.callback.analysis_endpoint}/prometheus"
metric = requests.get(prom_endpoint)
metric.raise_for_status()
return metric.text
return inner()
return app return app

View File

View File

View File

@ -0,0 +1,7 @@
import abc
class Connection(abc.ABC):
@abc.abstractmethod
def establish(self):
pass

View File

@ -0,0 +1,6 @@
from pyampq.connection.connection import Connection
class MockConnection(Connection):
def establish(self):
pass

View File

@ -0,0 +1,3 @@
class PikaConnection:
def establish(self):
pass

View File

@ -0,0 +1,13 @@
from pyinfra.pyampq.queue_manager.queue_manager import QueueManager
class Consumer:
def __init__(self, callback, queue_manager: QueueManager):
self.queue_manager = queue_manager
self.callback = callback
def consume_and_publish(self):
yield from self.queue_manager.consume_and_publish(self.callback)
def consume(self):
yield from self.queue_manager.consume()

View File

View File

@ -0,0 +1,102 @@
import json
import logging
import pika
from pyinfra.config import CONFIG
from pyinfra.pyampq.queue_manager.queue_manager import QueueManager, QueueHandle
logger = logging.getLogger("pika")
logger.setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
logger.setLevel(CONFIG.service.logging_level)
def monkey_patch_queue_handle(channel, queue) -> QueueHandle:
empty_message = (None, None, None)
def is_empty_message(message):
return message == empty_message
queue_handle = QueueHandle()
queue_handle.empty = lambda: is_empty_message(channel.basic_get(queue))
def produce_items():
while True:
message = channel.basic_get(queue)
if is_empty_message(message):
break
method_frame, properties, body = message
channel.basic_ack(method_frame.delivery_tag)
yield json.loads(body)
queue_handle.to_list = lambda: list(produce_items())
return queue_handle
def get_connection():
credentials = pika.PlainCredentials(username=CONFIG.rabbitmq.user, password=CONFIG.rabbitmq.password)
kwargs = {"host": CONFIG.rabbitmq.host, "port": CONFIG.rabbitmq.port, "credentials": credentials}
parameters = pika.ConnectionParameters(**kwargs)
connection = pika.BlockingConnection(parameters=parameters)
return connection
class PikaQueueManager(QueueManager):
def __init__(self, input_queue, output_queue):
super().__init__(input_queue, output_queue)
self.connection = get_connection()
self.channel = self.connection.channel()
self.channel.queue_declare(input_queue)
self.channel.queue_declare(output_queue)
def publish_request(self, request):
logger.debug(f"Publishing {request}")
self.channel.basic_publish("", self._input_queue, json.dumps(request))
def publish_response(self, message, callback):
logger.debug(f"Publishing response for {message}.")
frame, properties, body = message
response = json.dumps(callback(json.loads(body)))
self.channel.basic_publish("", self._output_queue, response)
self.channel.basic_ack(frame.delivery_tag)
def pull_request(self):
return self.channel.basic_get(self._input_queue)
def consume(self):
logger.debug("Consuming")
return self.channel.consume(self._input_queue)
def consume_and_publish(self, callback):
logger.info(f"Consuming with callback {callback.__name__}")
for message in self.consume():
self.publish_response(message, callback)
def clear(self):
self.channel.queue_purge(self._input_queue)
self.channel.queue_purge(self._output_queue)
@property
def input_queue(self) -> QueueHandle:
return monkey_patch_queue_handle(self.channel, self._input_queue)
@property
def output_queue(self) -> QueueHandle:
return monkey_patch_queue_handle(self.channel, self._output_queue)

View File

@ -0,0 +1,47 @@
import abc
class QueueHandle:
def empty(self) -> bool:
raise NotImplemented()
def to_list(self) -> list:
raise NotImplemented()
class QueueManager(abc.ABC):
def __init__(self, input_queue, output_queue):
self._input_queue = input_queue
self._output_queue = output_queue
@abc.abstractmethod
def publish_request(self, request):
pass
@abc.abstractmethod
def publish_response(self, response, callback):
pass
@abc.abstractmethod
def pull_request(self):
pass
@abc.abstractmethod
def consume(self):
pass
@abc.abstractmethod
def clear(self):
pass
@abc.abstractmethod
def input_queue(self) -> QueueHandle:
pass
@abc.abstractmethod
def output_queue(self) -> QueueHandle:
pass
@abc.abstractmethod
def consume_and_publish(self, callback):
pass

View File

@ -0,0 +1,43 @@
from pyinfra.pyampq.queue_manager.queue_manager import QueueManager, QueueHandle
from pyinfra.test.queue_mock import QueueMock
def monkey_patch_queue_handle(queue) -> QueueHandle:
queue_handle = QueueHandle()
queue_handle.empty = lambda: not queue
queue_handle.to_list = lambda: list(queue)
return queue_handle
class QueueManagerMock(QueueManager):
def __init__(self, input_queue, output_queue):
super().__init__(QueueMock(), QueueMock())
def publish_request(self, request):
self._input_queue.append(request)
def publish_response(self, message, callback):
self._output_queue.append(callback(message))
def pull_request(self):
return self._input_queue.popleft()
def consume(self):
while self._input_queue:
yield self.pull_request()
def consume_and_publish(self, callback):
for message in self.consume():
self.publish_response(message, callback)
def clear(self):
self._input_queue.clear()
self._output_queue.clear()
@property
def input_queue(self) -> QueueHandle:
return monkey_patch_queue_handle(self._input_queue)
@property
def output_queue(self) -> QueueHandle:
return monkey_patch_queue_handle(self._output_queue)

View File

@ -0,0 +1,5 @@
from collections import deque
class QueueMock(deque):
pass

View File

@ -6,20 +6,24 @@ import pytest
from pyinfra.exceptions import UnknownClient from pyinfra.exceptions import UnknownClient
from pyinfra.locations import TEST_DIR from pyinfra.locations import TEST_DIR
from pyinfra.pyampq.queue_manager.pika_queue_manager import PikaQueueManager
from pyinfra.pyampq.queue_manager.queue_manager import QueueManager
from pyinfra.storage.adapters.azure import AzureStorageAdapter from pyinfra.storage.adapters.azure import AzureStorageAdapter
from pyinfra.storage.adapters.s3 import S3StorageAdapter from pyinfra.storage.adapters.s3 import S3StorageAdapter
from pyinfra.storage.clients.azure import get_azure_client from pyinfra.storage.clients.azure import get_azure_client
from pyinfra.storage.clients.s3 import get_s3_client from pyinfra.storage.clients.s3 import get_s3_client
from pyinfra.storage.storage import Storage from pyinfra.storage.storage import Storage
from pyinfra.test.config import CONFIG from pyinfra.test.config import CONFIG
from pyinfra.test.queue_manager_mock import QueueManagerMock
from pyinfra.test.storage.adapter_mock import StorageAdapterMock from pyinfra.test.storage.adapter_mock import StorageAdapterMock
from pyinfra.test.storage.client_mock import StorageClientMock from pyinfra.test.storage.client_mock import StorageClientMock
from pyinfra.visitor import StorageStrategy, ForwardingStrategy, QueueVisitor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@pytest.fixture @pytest.fixture(scope="session")
def bucket_name(): def bucket_name():
return "pyinfra-test-bucket" return "pyinfra-test-bucket"
@ -51,7 +55,7 @@ def mock_make_load_data():
return load_data return load_data
@pytest.fixture(params=["minio", "aws"]) @pytest.fixture(params=["minio", "aws"], scope="session")
def storage(client_name, bucket_name, request): def storage(client_name, bucket_name, request):
logger.debug("Setup for storage") logger.debug("Setup for storage")
storage = Storage(get_adapter(client_name, request.param)) storage = Storage(get_adapter(client_name, request.param))
@ -71,3 +75,44 @@ def get_adapter(client_name, s3_backend):
return S3StorageAdapter(get_s3_client(CONFIG.storage[s3_backend])) return S3StorageAdapter(get_s3_client(CONFIG.storage[s3_backend]))
else: else:
raise UnknownClient(client_name) raise UnknownClient(client_name)
def get_queue_manager(queue_manager_name) -> QueueManager:
if queue_manager_name == "mock":
return QueueManagerMock("input", "output")
if queue_manager_name == "pika":
return PikaQueueManager("input", "output")
@pytest.fixture(scope="session")
def queue_manager(queue_manager_name):
queue_manager = get_queue_manager(queue_manager_name)
yield queue_manager
if queue_manager_name == "pika":
queue_manager.connection.close()
@pytest.fixture(scope="session")
def callback():
return lambda x: x * 2
@pytest.fixture
def analysis_callback(callback):
def inner(data: bytes):
return callback(data.decode())
return inner
@pytest.fixture
def response_strategy(response_strategy_name, storage):
if response_strategy_name == "storage":
return StorageStrategy(storage)
if response_strategy_name == "forwarding":
return ForwardingStrategy()
@pytest.fixture()
def visitor(storage, analysis_callback, response_strategy):
return QueueVisitor(storage, analysis_callback, response_strategy)

View File

@ -0,0 +1,75 @@
import gzip
import logging
from operator import itemgetter
import pytest
from pyinfra.pyampq.consumer import Consumer
from pyinfra.visitor import get_object_descriptor, ForwardingStrategy
@pytest.fixture(scope="session")
def consumer(queue_manager, callback):
return Consumer(callback, queue_manager)
@pytest.fixture(scope="session")
def access_callback():
return itemgetter("fileId")
@pytest.mark.parametrize("queue_manager_name", ["mock", "pika"], scope="session")
class TestConsumer:
def test_consuming_empty_input_queue_does_not_put_anything_on_output_queue(self, consumer, queue_manager):
queue_manager.clear()
consumer.consume()
assert queue_manager.output_queue.empty()
def test_consuming_nonempty_input_queue_puts_messages_on_output_queue_in_fifo_order(
self, consumer, queue_manager, callback
):
def produce_items():
return map(str, range(3))
queue_manager.clear()
for item in produce_items():
queue_manager.publish_request(item)
requests = consumer.consume()
for _, r in zip(produce_items(), requests):
queue_manager.publish_response(r, callback)
assert queue_manager.output_queue.to_list() == ["00", "11", "22"]
@pytest.mark.parametrize("client_name", ["mock", "s3", "azure"], scope="session")
@pytest.mark.parametrize("response_strategy_name", ["forwarding", "storage"], scope="session")
def test_consuming_nonempty_input_queue_with_visitor_puts_messages_on_output_queue_in_fifo_order(
self, consumer, queue_manager, visitor, bucket_name, storage
):
def produce_items():
for i in range(3):
body = {
"dossierId": "folder",
"fileId": f"file{i}",
"targetFileExtension": "in.gz",
"responseFileExtension": "out.gz",
}
yield f"{i}".encode(), body
visitor.response_strategy = ForwardingStrategy()
queue_manager.clear()
storage.clear_bucket(bucket_name)
for data, message in produce_items():
storage.put_object(**get_object_descriptor(message), data=gzip.compress(data))
queue_manager.publish_request(message)
requests = consumer.consume()
for _, r in zip(produce_items(), requests):
queue_manager.publish_response(r, visitor)
assert list(map(itemgetter("data"), queue_manager.output_queue.to_list())) == ["00", "11", "22"]

View File

@ -0,0 +1,38 @@
import gzip
import json
import pytest
from pyinfra.visitor import get_object_descriptor, get_response_object_descriptor
@pytest.fixture()
def body():
return {"dossierId": "folder", "fileId": "file", "targetFileExtension": "in.gz", "responseFileExtension": "out.gz"}
@pytest.mark.parametrize("client_name", ["mock", "azure", "s3"], scope="session")
class TestVisitor:
@pytest.mark.parametrize("response_strategy_name", ["forwarding", "storage"], scope="session")
def test_given_a_input_queue_message_callback_pulls_the_data_from_storage(
self, visitor, body, storage, bucket_name
):
storage.clear_bucket(bucket_name)
storage.put_object(**get_object_descriptor(body), data=gzip.compress(b"content"))
data_received = visitor.load_data(body)
assert b"content" == data_received
@pytest.mark.parametrize("response_strategy_name", ["forwarding", "storage"], scope="session")
def test_visitor_pulls_and_processes_data(self, visitor, body, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(**get_object_descriptor(body), data=gzip.compress("2".encode()))
response_body = visitor.load_and_process(body)
assert response_body["data"] == "22"
@pytest.mark.parametrize("response_strategy_name", ["storage"], scope="session")
def test_visitor_puts_response_on_storage(self, visitor, body, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(**get_object_descriptor(body), data=gzip.compress("2".encode()))
response_body = visitor(body)
assert "data" not in response_body
assert json.loads(gzip.decompress(storage.get_object(**get_response_object_descriptor(body))))["data"] == "22"

View File

@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@pytest.mark.parametrize("client_name", ["mock", "azure", "s3"]) @pytest.mark.parametrize("client_name", ["mock", "azure", "s3"], scope="session")
class TestStorage: class TestStorage:
def test_clearing_bucket_yields_empty_bucket(self, storage, bucket_name): def test_clearing_bucket_yields_empty_bucket(self, storage, bucket_name):
storage.clear_bucket(bucket_name) storage.clear_bucket(bucket_name)
@ -17,26 +17,31 @@ class TestStorage:
assert not {*data_received} assert not {*data_received}
def test_getting_object_put_in_bucket_is_object(self, storage, bucket_name): def test_getting_object_put_in_bucket_is_object(self, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(bucket_name, "file", b"content") storage.put_object(bucket_name, "file", b"content")
data_received = storage.get_object(bucket_name, "file") data_received = storage.get_object(bucket_name, "file")
assert b"content" == data_received assert b"content" == data_received
def test_getting_nested_object_put_in_bucket_is_nested_object(self, storage, bucket_name): def test_getting_nested_object_put_in_bucket_is_nested_object(self, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(bucket_name, "folder/file", b"content") storage.put_object(bucket_name, "folder/file", b"content")
data_received = storage.get_object(bucket_name, "folder/file") data_received = storage.get_object(bucket_name, "folder/file")
assert b"content" == data_received assert b"content" == data_received
def test_getting_objects_put_in_bucket_are_objects(self, storage, bucket_name): def test_getting_objects_put_in_bucket_are_objects(self, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(bucket_name, "file1", b"content 1") storage.put_object(bucket_name, "file1", b"content 1")
storage.put_object(bucket_name, "folder/file2", b"content 2") storage.put_object(bucket_name, "folder/file2", b"content 2")
data_received = storage.get_all_objects(bucket_name) data_received = storage.get_all_objects(bucket_name)
assert {b"content 1", b"content 2"} == {*data_received} assert {b"content 1", b"content 2"} == {*data_received}
def test_make_bucket_produces_bucket(self, storage, bucket_name): def test_make_bucket_produces_bucket(self, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.make_bucket(bucket_name) storage.make_bucket(bucket_name)
assert storage.has_bucket(bucket_name) assert storage.has_bucket(bucket_name)
def test_listing_bucket_files_yields_all_files_in_bucket(self, storage, bucket_name): def test_listing_bucket_files_yields_all_files_in_bucket(self, storage, bucket_name):
storage.clear_bucket(bucket_name)
storage.put_object(bucket_name, "file1", b"content 1") storage.put_object(bucket_name, "file1", b"content 1")
storage.put_object(bucket_name, "file2", b"content 2") storage.put_object(bucket_name, "file2", b"content 2")
full_names_received = storage.get_all_object_names(bucket_name) full_names_received = storage.get_all_object_names(bucket_name)

88
pyinfra/visitor.py Normal file
View File

@ -0,0 +1,88 @@
import abc
import gzip
import json
import logging
from operator import itemgetter
from typing import Callable
from pyinfra.config import CONFIG
from pyinfra.exceptions import DataLoadingFailure
from pyinfra.storage.storage import Storage
def get_object_name(body):
dossier_id, file_id, target_file_extension = itemgetter("dossierId", "fileId", "targetFileExtension")(body)
object_name = f"{dossier_id}/{file_id}.{target_file_extension}"
return object_name
def get_response_object_name(body):
dossier_id, file_id, response_file_extension = itemgetter("dossierId", "fileId", "responseFileExtension")(body)
object_name = f"{dossier_id}/{file_id}.{response_file_extension}"
return object_name
def get_object_descriptor(body):
return {"bucket_name": CONFIG.storage.bucket, "object_name": get_object_name(body)}
def get_response_object_descriptor(body):
return {"bucket_name": CONFIG.storage.bucket, "object_name": get_response_object_name(body)}
class ResponseStrategy(abc.ABC):
@abc.abstractmethod
def handle_response(self, body):
pass
def __call__(self, body):
return self.handle_response(body)
class StorageStrategy(ResponseStrategy):
def __init__(self, storage):
self.storage = storage
def handle_response(self, body):
self.storage.put_object(**get_response_object_descriptor(body), data=gzip.compress(json.dumps(body).encode()))
body.pop("data")
return body
class ForwardingStrategy(ResponseStrategy):
def handle_response(self, body):
return body
class QueueVisitor:
def __init__(self, storage: Storage, callback: Callable, response_strategy):
self.storage = storage
self.callback = callback
self.response_strategy = response_strategy
def load_data(self, body):
def download():
logging.debug(f"Downloading {object_descriptor}...")
data = self.storage.get_object(**object_descriptor)
logging.debug(f"Downloaded {object_descriptor}.")
return data
object_descriptor = get_object_descriptor(body)
try:
return gzip.decompress(download())
except Exception as err:
logging.warning(f"Loading data from storage failed for {object_descriptor}.")
raise DataLoadingFailure() from err
def process_data(self, data):
return self.callback(data)
def load_and_process(self, body):
data = self.process_data(self.load_data(body))
result_body = {**body, "data": data}
return result_body
def __call__(self, body):
result_body = self.load_and_process(body)
return self.response_strategy(result_body)

View File

@ -8,7 +8,7 @@ from pyinfra.callback import (
make_retry_callback, make_retry_callback,
make_callback_for_output_queue, make_callback_for_output_queue,
) )
from pyinfra.config import CONFIG from pyinfra.config import CONFIG, make_art
from pyinfra.consume import consume, ConsumerError from pyinfra.consume import consume, ConsumerError
from pyinfra.core import make_payload_processor, make_storage_data_loader, make_analyzer from pyinfra.core import make_payload_processor, make_storage_data_loader, make_analyzer
from pyinfra.flask import run_probing_webserver, set_up_probing_webserver from pyinfra.flask import run_probing_webserver, set_up_probing_webserver
@ -49,6 +49,7 @@ def make_callback():
def main(): def main():
# TODO: implement meaningful checks # TODO: implement meaningful checks
logging.info(make_art())
webserver = Process(target=run_probing_webserver, args=(set_up_probing_webserver(),)) webserver = Process(target=run_probing_webserver, args=(set_up_probing_webserver(),))
logging.info("Starting webserver...") logging.info("Starting webserver...")
webserver.start() webserver.start()