From 02699d615d3431674571beba6940020a72df60e6 Mon Sep 17 00:00:00 2001 From: Federico Busetti <729029+febus982@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:43:59 +0100 Subject: [PATCH] Update docs Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com> --- docs/event_class.md | 18 +++++ docs/protocol_bindings/http_binding.md | 91 +++++++++++++++++++------- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/docs/event_class.md b/docs/event_class.md index 501cac9..f9cb5df 100644 --- a/docs/event_class.md +++ b/docs/event_class.md @@ -104,6 +104,7 @@ When you create event types in your app you will want to make sure to follow the be kept up to date and make sure their validation, serialization and deserialization rules will be compliant with the [CloudEvents spec](https://github.com/cloudevents/spec/tree/main). +/// tab | class Syntax ```python from typing import TypedDict, Literal from cloudevents_pydantic.events import CloudEvent, field_types @@ -121,6 +122,23 @@ event = OrderCreated.event_factory( data={"a_str": "a nice string", "an_int": 1}, ) ``` +/// + +/// tab | inline Syntax +```python +from typing import TypedDict, Literal +from cloudevents_pydantic.events import CloudEvent, field_types + +class OrderCreated(CloudEvent): + data: TypedDict("OrderCreatedData", {"a_str": field_types.String, "an_int": field_types.Integer}) + type: Literal["order_created"] = "order_created" + source: field_types.String = "order_service" + +event = OrderCreated.event_factory( + data={"a_str": "a nice string", "an_int": 1}, +) +``` +/// /// admonition | Use subclasses type: warning diff --git a/docs/protocol_bindings/http_binding.md b/docs/protocol_bindings/http_binding.md index 48d650e..c6fd964 100644 --- a/docs/protocol_bindings/http_binding.md +++ b/docs/protocol_bindings/http_binding.md @@ -37,15 +37,14 @@ def do_something(): /// admonition | Why you have to reuse the same object? type: tip -When the HTTPHandler instance is created it creates internally a Pydantic `TypeAdapter` -for the event class, to handle efficiently event batches. This is an expensive operation. -Check the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/performance/#typeadapter-instantiated-once) -about this. +When the HTTPHandler instance is created it creates internally instances of Pydantic `TypeAdapter` +for the event class, to handle efficiently event serialization and discriminated unions. This is +an expensive operation. Check the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/performance/#typeadapter-instantiated-once) about this. /// -## Serialize a JSON event +## Deserialize a JSON event -HTTP serialization returns header and body to be used in a HTTP request. +HTTP deserialization parses the body to reconstruct the event. /// tab | Custom Event class ```python @@ -55,20 +54,15 @@ from cloudevents_pydantic.bindings.http import HTTPHandler class OrderCreated(CloudEvent): ... -minimal_attributes = { - "type": "order_created", - "source": "https://example.com/event-producer", - "id": "b96267e2-87be-4f7a-b87c-82f64360d954", - "specversion": "1.0", -} +single_event_json = '{"data":null,"source":"https://example.com/event-producer","id":"b96267e2-87be-4f7a-b87c-82f64360d954","type":"com.example.string","specversion":"1.0","time":"2022-07-16T12:03:20.519216+04:00","subject":null,"datacontenttype":null,"dataschema":null}' +batch_event_json = '[{"data":null,"source":"https://example.com/event-producer","id":"b96267e2-87be-4f7a-b87c-82f64360d954","type":"com.example.string","specversion":"1.0","time":"2022-07-16T12:03:20.519216+04:00","subject":null,"datacontenttype":null,"dataschema":null}]' http_handler = HTTPHandler(OrderCreated) -event = OrderCreated.event_factory(**minimal_attributes) # Single event -headers, body = http_handler.to_json(event) +event = http_handler.from_json(single_event_json) # Batch (list) of events -headers, body = http_handler.to_json_batch([event]) +batch_of_events = http_handler.from_json_batch(batch_event_json) ``` /// @@ -88,15 +82,57 @@ http_handler = HTTPHandler() event = CloudEvent.event_factory(**minimal_attributes) # Single event -json_string = http_handler.to_json(event) +event = http_handler.to_json(event) # Batch (list) of events -json_batch_string = http_handler.to_json_batch([event]) +batch_of_events = http_handler.to_json_batch([event]) ``` /// -## Deserialize a JSON event +/// details | Use discriminated Unions to handle multiple Event classes + type: warning -HTTP deserialization parses the body to reconstruct the event. +You'll want to use [discriminated unions](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions) +as event class and use a single `HTTPHandler` for multiple Event classes to be more efficient on validation +and to produce a correct schema. + +```python +from typing import Annotated, Literal, Union + +from pydantic import Field +from typing_extensions import TypedDict + +from cloudevents_pydantic.bindings.http import HTTPHandler +from cloudevents_pydantic.events import CloudEvent + + +class OrderCreatedEvent(CloudEvent): + data: TypedDict("OrderCreatedData", {"order_id": str}) + type: Literal["order_created"] + + +class CustomerCreatedEvent(CloudEvent): + data: TypedDict("CustomerCreatedData", {"customer_id": str}) + type: Literal["customer_created"] + + +Event = Annotated[ + Union[OrderCreatedEvent, CustomerCreatedEvent], + Field(discriminator="type"), +] + +http_handler = HTTPHandler(Event) + +customer_event_json = '{"data":{"customer_id":"123"},"source":"customer_service","id":"123","type":"customer_created","specversion":"1.0","time":null,"subject":null,"datacontenttype":null,"dataschema":null}' + +print(type(http_handler.from_json(customer_event_json))) +# +``` +/// + + +## Serialize a JSON event + +HTTP serialization returns header and body to be used in a HTTP request. /// tab | Custom Event class ```python @@ -106,15 +142,20 @@ from cloudevents_pydantic.bindings.http import HTTPHandler class OrderCreated(CloudEvent): ... -single_event_json = '{"data":null,"source":"https://example.com/event-producer","id":"b96267e2-87be-4f7a-b87c-82f64360d954","type":"com.example.string","specversion":"1.0","time":"2022-07-16T12:03:20.519216+04:00","subject":null,"datacontenttype":null,"dataschema":null}' -batch_event_json = '[{"data":null,"source":"https://example.com/event-producer","id":"b96267e2-87be-4f7a-b87c-82f64360d954","type":"com.example.string","specversion":"1.0","time":"2022-07-16T12:03:20.519216+04:00","subject":null,"datacontenttype":null,"dataschema":null}]' +minimal_attributes = { + "type": "order_created", + "source": "https://example.com/event-producer", + "id": "b96267e2-87be-4f7a-b87c-82f64360d954", + "specversion": "1.0", +} http_handler = HTTPHandler(OrderCreated) +event = OrderCreated.event_factory(**minimal_attributes) # Single event -event = http_handler.from_json(single_event_json) +headers, body = http_handler.to_json(event) # Batch (list) of events -batch_of_events = http_handler.from_json_batch(batch_event_json) +headers, body = http_handler.to_json_batch([event]) ``` /// @@ -134,8 +175,8 @@ http_handler = HTTPHandler() event = CloudEvent.event_factory(**minimal_attributes) # Single event -event = http_handler.to_json(event) +json_string = http_handler.to_json(event) # Batch (list) of events -batch_of_events = http_handler.to_json_batch([event]) +json_batch_string = http_handler.to_json_batch([event]) ``` ///