Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.12.0]

### Changed

- Dropped Python3.8 support while it has reached EOL. ([])

## [1.11.1]

### Fixed
- Kafka `conversion` marshaller and unmarshaller typings ([#240])
- Improved public API type annotations and fixed unit test type errors ([#248])

## [1.11.0]

### Fixed
Expand Down Expand Up @@ -287,3 +299,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#232]: https://github.com/cloudevents/sdk-python/pull/232
[#235]: https://github.com/cloudevents/sdk-python/pull/235
[#236]: https://github.com/cloudevents/sdk-python/pull/236
[#240]: https://github.com/cloudevents/sdk-python/pull/240
[#248]: https://github.com/cloudevents/sdk-python/pull/248
4 changes: 2 additions & 2 deletions samples/http-image-cloudevents/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
image_bytes = resp.content


def send_binary_cloud_event(url: str):
def send_binary_cloud_event(url: str) -> None:
# Create cloudevent
attributes = {
"type": "com.example.string",
Expand All @@ -41,7 +41,7 @@ def send_binary_cloud_event(url: str):
print(f"Sent {event['id']} of type {event['type']}")


def send_structured_cloud_event(url: str):
def send_structured_cloud_event(url: str) -> None:
# Create cloudevent
attributes = {
"type": "com.example.base64",
Expand Down
2 changes: 1 addition & 1 deletion src/cloudevents/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# License for the specific language governing permissions and limitations
# under the License.

__version__ = "1.11.0"
__version__ = "1.12.0"
2 changes: 1 addition & 1 deletion src/cloudevents/v1/abstract/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class CloudEvent:
@classmethod
def create(
cls: typing.Type[AnyCloudEvent],
attributes: typing.Dict[str, typing.Any],
attributes: typing.Mapping[str, typing.Any],
data: typing.Optional[typing.Any],
) -> AnyCloudEvent:
"""
Expand Down
6 changes: 4 additions & 2 deletions src/cloudevents/v1/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def from_json(

def from_http(
event_type: typing.Type[AnyCloudEvent],
headers: typing.Mapping[str, str],
headers: typing.Union[
typing.Mapping[str, str], types.SupportsDuplicateItems[str, str]
],
data: typing.Optional[typing.Union[str, bytes]],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> AnyCloudEvent:
Expand Down Expand Up @@ -260,7 +262,7 @@ def best_effort_encode_attribute_value(value: typing.Any) -> typing.Any:

def from_dict(
event_type: typing.Type[AnyCloudEvent],
event: typing.Dict[str, typing.Any],
event: typing.Mapping[str, typing.Any],
) -> AnyCloudEvent:
"""
Constructs an Event object of a given `event_type` from
Expand Down
6 changes: 4 additions & 2 deletions src/cloudevents/v1/http/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def from_json(


def from_http(
headers: typing.Dict[str, str],
headers: typing.Union[
typing.Mapping[str, str], types.SupportsDuplicateItems[str, str]
],
data: typing.Optional[typing.Union[str, bytes]],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> CloudEvent:
Expand All @@ -58,7 +60,7 @@ def from_http(


def from_dict(
event: typing.Dict[str, typing.Any],
event: typing.Mapping[str, typing.Any],
) -> CloudEvent:
"""
Constructs a CloudEvent from a dict `event` representation.
Expand Down
6 changes: 4 additions & 2 deletions src/cloudevents/v1/http/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ class CloudEvent(abstract.CloudEvent):

@classmethod
def create(
cls, attributes: typing.Dict[str, typing.Any], data: typing.Optional[typing.Any]
cls,
attributes: typing.Mapping[str, typing.Any],
data: typing.Optional[typing.Any],
) -> "CloudEvent":
return cls(attributes, data)

def __init__(self, attributes: typing.Dict[str, str], data: typing.Any = None):
def __init__(self, attributes: typing.Mapping[str, str], data: typing.Any = None):
"""
Event Constructor
:param attributes: a dict with cloudevent attributes. Minimally
Expand Down
65 changes: 55 additions & 10 deletions src/cloudevents/v1/kafka/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@
from cloudevents_v1.kafka.exceptions import KeyMapperError
from cloudevents_v1.sdk import types

DEFAULT_MARSHALLER: types.MarshallerType = json.dumps
DEFAULT_UNMARSHALLER: types.MarshallerType = json.loads
DEFAULT_EMBEDDED_DATA_MARSHALLER: types.MarshallerType = lambda x: x
JSON_MARSHALLER: types.MarshallerType = json.dumps
JSON_UNMARSHALLER: types.UnmarshallerType = json.loads
IDENTITY_MARSHALLER = IDENTITY_UNMARSHALLER = lambda x: x

DEFAULT_MARSHALLER: types.MarshallerType = JSON_MARSHALLER
DEFAULT_UNMARSHALLER: types.UnmarshallerType = JSON_UNMARSHALLER
DEFAULT_EMBEDDED_DATA_MARSHALLER: types.MarshallerType = IDENTITY_MARSHALLER
DEFAULT_EMBEDDED_DATA_UNMARSHALLER: types.UnmarshallerType = IDENTITY_UNMARSHALLER


class KafkaMessage(typing.NamedTuple):
Expand Down Expand Up @@ -106,11 +111,29 @@ def to_binary(
return KafkaMessage(headers, message_key, data)


@typing.overload
def from_binary(
message: KafkaMessage,
event_type: typing.Optional[typing.Type[AnyCloudEvent]] = None,
data_unmarshaller: typing.Optional[types.MarshallerType] = None,
event_type: None = None,
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> http.CloudEvent:
pass


@typing.overload
def from_binary(
message: KafkaMessage,
event_type: typing.Type[AnyCloudEvent],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> AnyCloudEvent:
pass


def from_binary(
message: KafkaMessage,
event_type: typing.Optional[typing.Type[AnyCloudEvent]] = None,
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> typing.Union[http.CloudEvent, AnyCloudEvent]:
"""
Returns a CloudEvent from a KafkaMessage in binary format.

Expand Down Expand Up @@ -139,10 +162,11 @@ def from_binary(
raise cloud_exceptions.DataUnmarshallerError(
f"Failed to unmarshall data with error: {type(e).__name__}('{e}')"
)
result: typing.Union[http.CloudEvent, AnyCloudEvent]
if event_type:
result = event_type.create(attributes, data)
else:
result = http.CloudEvent.create(attributes, data) # type: ignore
result = http.CloudEvent.create(attributes, data)
return result


Expand Down Expand Up @@ -205,12 +229,32 @@ def to_structured(
return KafkaMessage(headers, message_key, value)


@typing.overload
def from_structured(
message: KafkaMessage,
event_type: typing.Optional[typing.Type[AnyCloudEvent]] = None,
data_unmarshaller: typing.Optional[types.MarshallerType] = None,
event_type: None = None,
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
envelope_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> http.CloudEvent:
pass


@typing.overload
def from_structured(
message: KafkaMessage,
event_type: typing.Type[AnyCloudEvent],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
envelope_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> AnyCloudEvent:
pass


def from_structured(
message: KafkaMessage,
event_type: typing.Optional[typing.Type[AnyCloudEvent]] = None,
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
envelope_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> typing.Union[http.CloudEvent, AnyCloudEvent]:
"""
Returns a CloudEvent from a KafkaMessage in structured format.

Expand All @@ -222,7 +266,7 @@ def from_structured(
:returns: CloudEvent
"""

data_unmarshaller = data_unmarshaller or DEFAULT_EMBEDDED_DATA_MARSHALLER
data_unmarshaller = data_unmarshaller or DEFAULT_EMBEDDED_DATA_UNMARSHALLER
envelope_unmarshaller = envelope_unmarshaller or DEFAULT_UNMARSHALLER
try:
structure = envelope_unmarshaller(message.value)
Expand Down Expand Up @@ -259,8 +303,9 @@ def from_structured(
attributes["datacontenttype"] = val.decode()
else:
attributes[header.lower()] = val.decode()
result: typing.Union[AnyCloudEvent, http.CloudEvent]
if event_type:
result = event_type.create(attributes, data)
else:
result = http.CloudEvent.create(attributes, data) # type: ignore
result = http.CloudEvent.create(attributes, data)
return result
6 changes: 4 additions & 2 deletions src/cloudevents/v1/pydantic/v1/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@


def from_http(
headers: typing.Dict[str, str],
headers: typing.Union[
typing.Mapping[str, str], types.SupportsDuplicateItems[str, str]
],
data: typing.Optional[typing.AnyStr],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> CloudEvent:
Expand Down Expand Up @@ -63,7 +65,7 @@ def from_json(


def from_dict(
event: typing.Dict[str, typing.Any],
event: typing.Mapping[str, typing.Any],
) -> CloudEvent:
"""
Construct an CloudEvent from a dict `event` representation.
Expand Down
6 changes: 4 additions & 2 deletions src/cloudevents/v1/pydantic/v1/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ class CloudEvent(abstract.CloudEvent, BaseModel): # type: ignore

@classmethod
def create(
cls, attributes: typing.Dict[str, typing.Any], data: typing.Optional[typing.Any]
cls,
attributes: typing.Mapping[str, typing.Any],
data: typing.Optional[typing.Any],
) -> "CloudEvent":
return cls(attributes, data)

Expand Down Expand Up @@ -157,7 +159,7 @@ def create(

def __init__( # type: ignore[no-untyped-def]
self,
attributes: typing.Optional[typing.Dict[str, typing.Any]] = None,
attributes: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Optional[typing.Any] = None,
**kwargs,
):
Expand Down
6 changes: 4 additions & 2 deletions src/cloudevents/v1/pydantic/v2/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@


def from_http(
headers: typing.Dict[str, str],
headers: typing.Union[
typing.Mapping[str, str], types.SupportsDuplicateItems[str, str]
],
data: typing.Optional[typing.AnyStr],
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
) -> CloudEvent:
Expand Down Expand Up @@ -64,7 +66,7 @@ def from_json(


def from_dict(
event: typing.Dict[str, typing.Any],
event: typing.Mapping[str, typing.Any],
) -> CloudEvent:
"""
Construct an CloudEvent from a dict `event` representation.
Expand Down
8 changes: 6 additions & 2 deletions src/cloudevents/v1/pydantic/v2/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class CloudEvent(abstract.CloudEvent, BaseModel): # type: ignore

@classmethod
def create(
cls, attributes: typing.Dict[str, typing.Any], data: typing.Optional[typing.Any]
cls,
attributes: typing.Mapping[str, typing.Any],
data: typing.Optional[typing.Any],
) -> "CloudEvent":
return cls(attributes, data)

Expand Down Expand Up @@ -102,7 +104,7 @@ def create(

def __init__( # type: ignore[no-untyped-def]
self,
attributes: typing.Optional[typing.Dict[str, typing.Any]] = None,
attributes: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Optional[typing.Any] = None,
**kwargs,
):
Expand Down Expand Up @@ -172,6 +174,8 @@ def model_validate_json(
*,
strict: typing.Optional[bool] = None,
context: typing.Optional[typing.Dict[str, Any]] = None,
by_alias: typing.Optional[bool] = None,
by_name: typing.Optional[bool] = None,
) -> "CloudEvent":
return conversion.from_json(cls, json_data)

Expand Down
23 changes: 14 additions & 9 deletions src/cloudevents/v1/sdk/event/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import annotations

import typing

from cloudevents_v1.sdk.event import base, opt

if typing.TYPE_CHECKING:
from typing_extensions import Self


class Event(base.BaseEvent):
_ce_required_fields = {"id", "source", "type", "specversion"}
Expand Down Expand Up @@ -79,39 +84,39 @@ def Extensions(self) -> dict:
return {}
return dict(result)

def SetEventType(self, eventType: str) -> base.BaseEvent:
def SetEventType(self, eventType: str) -> Self:
self.Set("type", eventType)
return self

def SetSource(self, source: str) -> base.BaseEvent:
def SetSource(self, source: str) -> Self:
self.Set("source", source)
return self

def SetEventID(self, eventID: str) -> base.BaseEvent:
def SetEventID(self, eventID: str) -> Self:
self.Set("id", eventID)
return self

def SetEventTime(self, eventTime: typing.Optional[str]) -> base.BaseEvent:
def SetEventTime(self, eventTime: typing.Optional[str]) -> Self:
self.Set("time", eventTime)
return self

def SetSubject(self, subject: typing.Optional[str]) -> base.BaseEvent:
def SetSubject(self, subject: typing.Optional[str]) -> Self:
self.Set("subject", subject)
return self

def SetSchema(self, schema: typing.Optional[str]) -> base.BaseEvent:
def SetSchema(self, schema: typing.Optional[str]) -> Self:
self.Set("dataschema", schema)
return self

def SetContentType(self, contentType: typing.Optional[str]) -> base.BaseEvent:
def SetContentType(self, contentType: typing.Optional[str]) -> Self:
self.Set("datacontenttype", contentType)
return self

def SetData(self, data: typing.Optional[object]) -> base.BaseEvent:
def SetData(self, data: typing.Optional[object]) -> Self:
self.Set("data", data)
return self

def SetExtensions(self, extensions: typing.Optional[dict]) -> base.BaseEvent:
def SetExtensions(self, extensions: typing.Optional[dict]) -> Self:
self.Set("extensions", extensions)
return self

Expand Down
Loading