Welcome to Happyly’s documentation!¶
Introduction¶
Happyly is a scalable solution for systems which handle any kind of messages. Have you ever seen a codebase where serialization, acknowledgement and business logic are mixed together like a spaghetti? I have. Imagine switching between Google Pub/Sub and Django REST Framework. Or Celery. This shouldn’t be a nightmare but it often is.
Here’s the approach of Happyly:
Write you business logic in universal Handlers, which don’t care at all how you serialize things or send them over network or deal with message queues.
Describe your schemas using ORM/Framework-agnostic technology.
Plug-in any details of messaging protocol, serialization and networking. Change them with different drop-in replacements at any time.
Use cases¶
Google Pub/Sub
Let’s be honest, the official Python client library is too low-level. You must serialize and deserialize things manually, as well as to
ack
andnack
messages.Usual way:
def callback(message): attributes = json.loads(message.data) try: result = process_things(attributes['ID']) encoded = json.dumps(result).encode('utf-8') PUBLISHER.publish(TOPIC, encoded) except NeedToRetry: _LOGGER.info('Not acknowledging, will retry later.') except Exception: _LOGGER.error('An error occured') message.ack() else: message.ack()
Happyly way:
class MyHandler(happyly.handler): def handle(attributes: dict): return process_things(attributes['ID']) def on_handling_failed(attributes: dict, error): if isinstance(error, NeedToRetry): raise error from error else: _LOGGER.error('An error occured')
MyHandler
is now also usable with Celery or Flask. Or with yaml serialization. Or withmessage.attributes
instead ofmessage.data
. Without any change.You are going to change messaging technology later.
Easy! Here’s an example.
Define your message schemas.
class MyInputSchema(happyly.Schema): request_id = marshmallow.fields.Str(required=True) class MyOutputSchema(happyly.Schema): request_id = marshmallow.fields.Str(required=True) result = marshmallow.fields.Str(required=True) error = marshmallow.fields.Str()
Define your handler
class ProcessThings(happyly.Handler): def handle(message: dict): req_id = message['request_id'] if req_id in ALLOWED: result = get_result_for_id(req_id) else: result = 'not allowed' return { 'request_id': req_id 'result': result } def on_handling_failed(message: dict, error): return { 'request_id': message['request_id'] 'result': 'error', 'error': str(error) }
Plug it into Celery:
@celery.task('hello') def hello(message): result = happyly.Executor( handler=ProcessThings(), serializer=happyly.DummyValidator(schema=MyInputSchema()), deserializer=happyly.DummyValidator(schema=MyOutputSchema()), ).run_for_result( message ) return result
Or Google Pub/Sub:
happyly.Listener( handler=ProcessThings(), deserializer=happyly.google_pubsub.JSONDeserializerWithRequestIdRequired( schema=MyInputSchema() ), serializer=happyly.google_pubsub.BinaryJSONSerializer( schema=MyOutputSchema() ), publisher=happyly.google_pubsub.GooglePubSubPublisher( topic='my_topic', project='my_project', ), ).start_listening()
5. Move to any other technology. Or swap serializer to another. Do whatever you need while your handler and schemas remain absolutely the same.
Installation¶
Happyly is hosted on PyPI, so you can use:
pip install happyly
There is an extra dependency which enables cached components via Redis. If you need it, install it like this:
pip install happyly[redis]
Concepts¶
Advanced¶
API Reference¶
|
|