def test_publish_one_message(self): self.graph.sns_producer.produce(created("foo"), data="data") assert_that( self.graph.sns_producer, published(has_media_type(created("foo")), ), )
def make_media_type(self, event_info, discard_event_type=False): if discard_event_type: return created("{}".format(name_for( self.event_store.model_class), )) return created("{}.{}".format( name_for(self.event_store.model_class), event_info.event.event_type.name, ))
def test_publish_one_message(self): self.graph.sns_producer.produce(created("foo"), data="data") assert_that( self.graph.sns_producer, published( has_media_type(created("foo")), ), )
def test_publish_one_message(self): self.graph.sns_producer.produce(created("foo"), uri="http://localhost") assert_that( self.graph.sns_producer, published( all_of( has_media_type(created("foo")), has_uri("http://localhost"), ), ), )
class CustomMessageSchema(URIMessageSchema): """ Message indicating that a resource was created """ MEDIA_TYPE = created("Resource") enumField = EnumField(TestEnum, attribute="enum_field", required=True)
def test_publish_by_convention(): """ Message publishing can use this convention. """ def loader(metadata): return dict(sns_topic_arns=dict(default="default", )) graph = create_object_graph("example", testing=True, loader=loader) graph.sns_producer.produce(created("foo"), uri="http://example.com", opaque_data=dict()) assert_that(graph.sns_producer.sns_client.publish.call_count, is_(equal_to(1))) assert_that(graph.sns_producer.sns_client.publish.call_args[1]["TopicArn"], is_(equal_to("default"))) assert_that( loads(graph.sns_producer.sns_client.publish.call_args[1]["Message"]), is_( equal_to({ "mediaType": "application/vnd.globality.pubsub._.created.foo", "uri": "http://example.com", "opaqueData": {}, })))
def test_codec_sqs_envelope(): graph = create_object_graph("example", testing=True) consumer = None message_id = "message_id" receipt_handle = "receipt_handle" envelope = CodecSQSEnvelope(graph) media_type = created("foo") uri = "http://foo/id" sqs_message = envelope.parse_raw_message(consumer, dict( MessageId=message_id, ReceiptHandle=receipt_handle, Body=dumps(dict( Message=dumps(dict( mediaType=media_type, foo="bar", uri=uri, )), )), )) assert_that(sqs_message.content, is_(equal_to(dict( # NB: no foo key here because it's not part of the schema media_type=media_type, uri=uri, )))) assert_that(sqs_message.media_type, is_(equal_to(media_type))) assert_that(sqs_message.message_id, is_(equal_to(message_id))) assert_that(sqs_message.receipt_handle, is_(equal_to(receipt_handle)))
def write(args): loader = load_from_dict( sns_producer=dict( profile_name=args.profile, region_name=args.region, ), sns_topic_arns=dict(default=args.topic_arn, ), ) graph = create_object_graph("pubsub", debug=args.debug, loader=loader) graph.use("pubsub_message_schema_registry") graph.use("sns_topic_arns") graph.use("sns_producer") graph.lock() if args.media_type.startswith("application"): media_type = args.media_type else: media_type = created(args.media_type) message_id = graph.sns_producer.produce( media_type=media_type, uri=args.uri, ) print("Wrote SNS message: {}".format(message_id)) # noqa
def test_publish_two_messages_non_strict(self): self.graph.sns_producer.produce(created("foo"), uri="http://localhost") self.graph.sns_producer.produce(created("bar"), uri="http://localhost") assert_that( self.graph.sns_producer, published_inanyorder( all_of( has_media_type(created("bar")), has_uri("http://localhost"), ), all_of( has_media_type(created("foo")), has_uri(), ), ), )
class MessageBatchSchema(PubSubMessageSchema): """ A message indicating that a batch of messages needs to be published. """ MEDIA_TYPE = created("batch_message") messages = fields.List(fields.Nested(BatchedMessageSchema), required=True)
class MessageBatchSchema(PubSubMessageSchema): """ A message indicating that a batch of messages needs to be published. """ MEDIA_TYPE = created("batch_message") messages = fields.List(fields.Nested(BatchedMessageSchema), required=True) def deserialize_media_type(self, obj): return MessageBatchSchema.MEDIA_TYPE
def test_produce_custom_topic_environ(): """ Can set a custom topic via environment """ key = "EXAMPLE__SNS_TOPIC_ARNS__CREATED__FOO__BAR_BAZ" environ[key] = "topic" graph = create_object_graph("example", testing=True, loader=load_from_environ) graph.sns_producer.produce(created("foo.bar_baz"), bar="baz") assert_that(graph.sns_producer.sns_client.publish.call_count, is_(equal_to(1))) assert_that(graph.sns_producer.sns_client.publish.call_args[1]["TopicArn"], is_(equal_to("topic")))
def test_created(self): assert_that( created("Foo.Bar"), is_(equal_to("application/vnd.globality.pubsub._.created.foo.bar")), ) assert_that( self.graph.pubsub_message_schema_registry.find( "application/vnd.globality.pubsub._.created.foo.bar", ).schema, is_(instance_of(URIMessageSchema)), )
def test_created(self): assert_that( created("Foo.Bar"), is_(equal_to( "application/vnd.globality.pubsub._.created.foo.bar")), ) assert_that( self.graph.pubsub_message_schema_registry.find( "application/vnd.globality.pubsub._.created.foo.bar", ).schema, is_(instance_of(URIMessageSchema)), )
def test_handle_message_ignored(self): """ Unsupported media types are ignored. """ self.message.media_type = created("bar") assert_that( self.dispatcher.handle_message( message=self.message, bound_handlers=self.daemon.bound_handlers, ), has_properties( elapsed_time=greater_than(0.0), result=MessageHandlingResultType.IGNORED, ), )
def test_dispatch_by_convention(): """ Message dispatch can use this convention. """ graph = create_object_graph("example", testing=True) media_type = created(Foo) assert_that( graph.pubsub_message_schema_registry[media_type].schema, is_(instance_of(URIMessageSchema)), ) assert_that( graph.sqs_message_handler_registry[media_type], is_(equal_to(noop_handler)), )
def test_dispatch_by_uri_convention(): """ Message dispatch can use this convention. """ daemon = ExampleDaemon.create_for_testing() graph = daemon.graph media_type = created(Foo) assert_that( graph.pubsub_message_schema_registry.find(media_type).schema, is_(instance_of(URIMessageSchema)), ) assert_that( graph.sqs_message_handler_registry.find(media_type, daemon.bound_handlers), is_(equal_to(noop_handler)), )
def test_publish_by_uri_convention(): """ Message publishing can use this convention. """ def loader(metadata): return dict(sns_topic_arns=dict(default="default", )) graph = create_object_graph("example", testing=True, loader=loader) published_time = str(time()) with patch("microcosm_pubsub.producer.time") as mocked_time: mocked_time.return_value = published_time graph.sns_producer.produce(created("foo"), uri="http://example.com", opaque_data=dict()) assert_that(graph.sns_producer.sns_client.publish.call_count, is_(equal_to(1))) assert_that(graph.sns_producer.sns_client.publish.call_args[1]["TopicArn"], is_(equal_to("default"))) assert_that( loads(graph.sns_producer.sns_client.publish.call_args[1]["Message"]), is_( equal_to({ "mediaType": "application/vnd.globality.pubsub._.created.foo", "uri": "http://example.com", "opaqueData": { "X-Request-Published": published_time, }, }))) assert_that( graph.sns_producer.sns_client.publish.call_args[1] ["MessageAttributes"], is_( equal_to({ "media_type": { "DataType": "String", "StringValue": "application/vnd.globality.pubsub._.created.foo" }, })))
def test_handle_with_skipping(): """ Test that skipping works """ daemon = ExampleDaemon.create_for_testing() graph = daemon.graph content = dict(bar="baz") result = graph.sqs_message_dispatcher.handle_message( message=SQSMessage( consumer=None, content=content, media_type=created("bar"), message_id=MESSAGE_ID, receipt_handle=None, ), bound_handlers=daemon.bound_handlers, ) assert_that(result, is_(equal_to(False)))
def test_publish_by_uri_convention(): """ Message publishing can use this convention. """ def loader(metadata): return dict( sns_topic_arns=dict( default="default", ) ) graph = create_object_graph("example", testing=True, loader=loader) graph.sns_producer.produce(created("foo"), uri="http://example.com", opaque_data=dict()) assert_that(graph.sns_producer.sns_client.publish.call_count, is_(equal_to(1))) assert_that(graph.sns_producer.sns_client.publish.call_args[1]["TopicArn"], is_(equal_to("default"))) assert_that(loads(graph.sns_producer.sns_client.publish.call_args[1]["Message"]), is_(equal_to({ "mediaType": "application/vnd.globality.pubsub._.created.foo", "uri": "http://example.com", "opaqueData": {}, })))
def test_codec_sqs_envelope(): graph = create_object_graph("example", testing=True) consumer = None message_id = "message_id" receipt_handle = "receipt_handle" envelope = CodecSQSEnvelope(graph) media_type = created("foo") uri = "http://foo/id" sqs_message = envelope.parse_raw_message( consumer, dict( MessageId=message_id, ReceiptHandle=receipt_handle, Body=dumps( dict(Message=dumps( dict( mediaType=media_type, foo="bar", uri=uri, )), )), )) assert_that( sqs_message.content, is_( equal_to( dict( # NB: no foo key here because it's not part of the schema media_type=media_type, uri=uri, )))) assert_that(sqs_message.media_type, is_(equal_to(media_type))) assert_that(sqs_message.message_id, is_(equal_to(message_id))) assert_that(sqs_message.receipt_handle, is_(equal_to(receipt_handle)))
return DerivedSchema.MEDIA_TYPE @schema class DuckTypeSchema(Schema): """ A duck typed schema """ MEDIA_TYPE = "application/vnd.microcosm.duck" quack = fields.String() @handles(DuckTypeSchema) @handles(created("foo")) @handles(deleted("foo")) @handles(DerivedSchema.MEDIA_TYPE) def noop_handler(message): return True @handles(created("IgnoredResource")) def skipping_handler(message): raise SkipMessage("Failed") class ExampleDaemon(ConsumerDaemon): @property def name(self):
""" from json import dumps, loads from hamcrest import (assert_that, equal_to, instance_of, is_) from microcosm.api import create_object_graph from microcosm_pubsub.codecs import PubSubMessageCodec from microcosm_pubsub.conventions import created, make_media_type, URIMessageSchema from microcosm_pubsub.decorators import handles class Foo(object): pass @handles(created("foo")) def noop_handler(message): return True def test_make_media_type(): """ Media type construction should generate the correct type strings. """ cases = [ (("foo", ), dict(), "application/vnd.globality.pubsub._.created.foo"), (("foo", ), dict(public=True), "application/vnd.globality.pubsub.created.foo"), (("foo", ), dict(organization="example"), "application/vnd.example.pubsub._.created.foo"),
from microcosm.api import binding from microcosm_pubsub.chain import assign from microcosm_fastapi.pubsub.handlers.chain_handlers import ChainURIHandlerAsync from microcosm_fastapi.pubsub.chain.chain import ChainAsync from microcosm_logging.decorators import logger class PizzaModel: def __init__(self, toppings): self.toppings = toppings @logger @binding("pizza_daemon_handler") @handles(created("Pizza")) class PizzaDaemonHandler(ChainURIHandlerAsync): def get_chain(self): return ChainAsync( assign("pizza.toppings").to("toppings"), self.process_topping_sync, self.process_topping_async, ) def process_topping_sync(self, toppings): return toppings + "_processed" async def process_topping_async(self, toppings): if toppings == "pineapple": raise ValueError() return toppings + "_processed"
return DerivedSchema.MEDIA_TYPE @schema class DuckTypeSchema(Schema): """ A duck typed schema """ MEDIA_TYPE = "application/vnd.microcosm.duck" quack = fields.String() @handles(DuckTypeSchema) @handles(created("foo")) @handles(deleted("foo")) @handles(DerivedSchema.MEDIA_TYPE) def noop_handler(message): return True @handles(created("IgnoredResource")) def skipping_handler(message): raise SkipMessage("Failed") class ExampleDaemon(ConsumerDaemon): @property def name(self): return "example"
Passes message context. """ headers = self.sqs_message_context(message) response = get(uri, headers=headers) if response.status_code == codes.not_found and self.nack_if_not_found: raise Nack(self.nack_timeout) response.raise_for_status() return response.json() def handle(self, message, uri, resource): return True @binding("publish_message_batch") @handles(created("BatchMessage")) @logger class PublishBatchMessage: def __init__(self, graph): self.sns_producer = graph.sns_producer def __call__(self, message): messages = message["messages"] for message in messages: self.sns_producer.publish_message( message["media_type"], message["message"], message["topic_arn"], message["opaque_data"], )
from urllib.parse import urljoin import requests from microcosm.decorators import binding from microcosm_logging.decorators import logger from microcosm_pubsub.chain import Chain, assign, extracts from microcosm_pubsub.conventions import created from microcosm_pubsub.decorators import handles from microcosm_pubsub.errors import Nack, SkipMessage from microcosm_pubsub.handlers import ChainURIHandler from rob_onboarding.models.order_event_type import OrderEventType @binding("order_fulfilled_handler") @handles(created("DeliveryEvent.PizzaDelivered")) @logger class OrderFulfilledHandler(ChainURIHandler): def __init__(self, graph): super().__init__(graph) self.server_uri = "http://localhost:5000" @property def resource_name(self): return "delivery_event" def get_chain(self): return Chain( assign("delivery_event.orderId").to("order_id"), self.extract_order, assign("order.customerId").to("customer_id"),