Skip to content

Commit 5e00c4f

Browse files
authored
Introduce typings (#207)
* chore: Add pre-commit hook Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: address typing issues Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: add py.typed meta Signed-off-by: Yurii Serhiichuk <[email protected]> * Add Pydantic plugin Signed-off-by: Yurii Serhiichuk <[email protected]> * Add Pydantic dependency Signed-off-by: Yurii Serhiichuk <[email protected]> * Add MyPy best practices configs Signed-off-by: Yurii Serhiichuk <[email protected]> * Add deprecation MyPy ignore Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: more typing fixes Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: more typings and explicit optionals Signed-off-by: Yurii Serhiichuk <[email protected]> * Use lowest-supported Python version Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: Fix silly `dict` and other MyPy-related issues. We're now explicitly ensuring codebase supports Python3.7+ Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: ignore typing limitation Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: `not` with `dict` returns `false` for an empty dict, so use `is None` check Signed-off-by: Yurii Serhiichuk <[email protected]> * deps: Update hooks Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: Make sure only non-callable unmarshallers are flagged Signed-off-by: Yurii Serhiichuk <[email protected]> * chore: Have some coverage slack Signed-off-by: Yurii Serhiichuk <[email protected]> * deps: bump pre-commit-hooks Signed-off-by: Yurii Serhiichuk <[email protected]> * ci: make sure py.typed is included into the bundle Signed-off-by: Yurii Serhiichuk <[email protected]> * docs: improve setup.py setup and add missing package metadata Signed-off-by: Yurii Serhiichuk <[email protected]> Signed-off-by: Yurii Serhiichuk <[email protected]>
1 parent a02864e commit 5e00c4f

33 files changed

+457
-350
lines changed

.pre-commit-config.yaml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.3.0
3+
rev: v4.4.0
44
hooks:
55
- id: trailing-whitespace
66
- id: end-of-file-fixer
77
- id: check-toml
88
- repo: https://github.com/pycqa/isort
9-
rev: 5.10.1
9+
rev: 5.11.4
1010
hooks:
1111
- id: isort
1212
args: [ "--profile", "black", "--filter-files" ]
1313
- repo: https://github.com/psf/black
14-
rev: 22.10.0
14+
rev: 22.12.0
1515
hooks:
1616
- id: black
1717
language_version: python3.10
18+
- repo: https://github.com/pre-commit/mirrors-mypy
19+
rev: "v0.991"
20+
hooks:
21+
- id: mypy
22+
files: ^(cloudevents/)
23+
exclude: ^(cloudevents/tests/)
24+
types: [ python ]
25+
args: [ ]
26+
additional_dependencies:
27+
- 'pydantic'

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include README.md
2+
include CHANGELOG.md
3+
include LICENSE
4+
include cloudevents/py.typed

cloudevents/abstract/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414

1515
from cloudevents.abstract.event import AnyCloudEvent, CloudEvent
1616

17-
__all__ = [AnyCloudEvent, CloudEvent]
17+
__all__ = ["AnyCloudEvent", "CloudEvent"]

cloudevents/abstract/event.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from types import MappingProxyType
1818
from typing import Mapping
1919

20+
AnyCloudEvent = typing.TypeVar("AnyCloudEvent", bound="CloudEvent")
21+
2022

2123
class CloudEvent:
2224
"""
@@ -29,10 +31,10 @@ class CloudEvent:
2931

3032
@classmethod
3133
def create(
32-
cls,
34+
cls: typing.Type[AnyCloudEvent],
3335
attributes: typing.Dict[str, typing.Any],
3436
data: typing.Optional[typing.Any],
35-
) -> "AnyCloudEvent":
37+
) -> AnyCloudEvent:
3638
"""
3739
Creates a new instance of the CloudEvent using supplied `attributes`
3840
and `data`.
@@ -70,7 +72,7 @@ def _get_attributes(self) -> typing.Dict[str, typing.Any]:
7072
raise NotImplementedError()
7173

7274
@abstractmethod
73-
def _get_data(self) -> typing.Optional[typing.Any]:
75+
def get_data(self) -> typing.Optional[typing.Any]:
7476
"""
7577
Returns the data of the event.
7678
@@ -85,7 +87,7 @@ def _get_data(self) -> typing.Optional[typing.Any]:
8587

8688
def __eq__(self, other: typing.Any) -> bool:
8789
if isinstance(other, CloudEvent):
88-
same_data = self._get_data() == other._get_data()
90+
same_data = self.get_data() == other.get_data()
8991
same_attributes = self._get_attributes() == other._get_attributes()
9092
return same_data and same_attributes
9193
return False
@@ -140,7 +142,4 @@ def __contains__(self, key: str) -> bool:
140142
return key in self._get_attributes()
141143

142144
def __repr__(self) -> str:
143-
return str({"attributes": self._get_attributes(), "data": self._get_data()})
144-
145-
146-
AnyCloudEvent = typing.TypeVar("AnyCloudEvent", bound=CloudEvent)
145+
return str({"attributes": self._get_attributes(), "data": self.get_data()})

cloudevents/conversion.py

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from cloudevents.sdk.event import v1, v03
2424

2525

26-
def _best_effort_serialize_to_json(
26+
def _best_effort_serialize_to_json( # type: ignore[no-untyped-def]
2727
value: typing.Any, *args, **kwargs
2828
) -> typing.Optional[typing.Union[bytes, str, typing.Any]]:
2929
"""
@@ -43,18 +43,18 @@ def _best_effort_serialize_to_json(
4343
return value
4444

4545

46-
_default_marshaller_by_format = {
46+
_default_marshaller_by_format: typing.Dict[str, types.MarshallerType] = {
4747
converters.TypeStructured: lambda x: x,
4848
converters.TypeBinary: _best_effort_serialize_to_json,
49-
} # type: typing.Dict[str, types.MarshallerType]
49+
}
5050

5151
_obj_by_version = {"1.0": v1.Event, "0.3": v03.Event}
5252

5353

5454
def to_json(
5555
event: AnyCloudEvent,
56-
data_marshaller: types.MarshallerType = None,
57-
) -> typing.Union[str, bytes]:
56+
data_marshaller: typing.Optional[types.MarshallerType] = None,
57+
) -> bytes:
5858
"""
5959
Converts given `event` to a JSON string.
6060
@@ -69,7 +69,7 @@ def to_json(
6969
def from_json(
7070
event_type: typing.Type[AnyCloudEvent],
7171
data: typing.Union[str, bytes],
72-
data_unmarshaller: types.UnmarshallerType = None,
72+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
7373
) -> AnyCloudEvent:
7474
"""
7575
Parses JSON string `data` into a CloudEvent.
@@ -91,9 +91,9 @@ def from_json(
9191

9292
def from_http(
9393
event_type: typing.Type[AnyCloudEvent],
94-
headers: typing.Dict[str, str],
95-
data: typing.Union[str, bytes, None],
96-
data_unmarshaller: types.UnmarshallerType = None,
94+
headers: typing.Mapping[str, str],
95+
data: typing.Optional[typing.Union[str, bytes]],
96+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
9797
) -> AnyCloudEvent:
9898
"""
9999
Parses CloudEvent `data` and `headers` into an instance of a given `event_type`.
@@ -133,14 +133,14 @@ def from_http(
133133
except json.decoder.JSONDecodeError:
134134
raise cloud_exceptions.MissingRequiredFields(
135135
"Failed to read specversion from both headers and data. "
136-
f"The following can not be parsed as json: {data}"
136+
"The following can not be parsed as json: {!r}".format(data)
137137
)
138138
if hasattr(raw_ce, "get"):
139139
specversion = raw_ce.get("specversion", None)
140140
else:
141141
raise cloud_exceptions.MissingRequiredFields(
142142
"Failed to read specversion from both headers and data. "
143-
f"The following deserialized data has no 'get' method: {raw_ce}"
143+
"The following deserialized data has no 'get' method: {}".format(raw_ce)
144144
)
145145

146146
if specversion is None:
@@ -152,7 +152,7 @@ def from_http(
152152

153153
if event_handler is None:
154154
raise cloud_exceptions.InvalidRequiredFields(
155-
f"Found invalid specversion {specversion}"
155+
"Found invalid specversion {}".format(specversion)
156156
)
157157

158158
event = marshall.FromRequest(
@@ -163,20 +163,19 @@ def from_http(
163163
attrs.pop("extensions", None)
164164
attrs.update(**event.extensions)
165165

166+
result_data: typing.Optional[typing.Any] = event.data
166167
if event.data == "" or event.data == b"":
167168
# TODO: Check binary unmarshallers to debug why setting data to ""
168-
# returns an event with data set to None, but structured will return ""
169-
data = None
170-
else:
171-
data = event.data
172-
return event_type.create(attrs, data)
169+
# returns an event with data set to None, but structured will return ""
170+
result_data = None
171+
return event_type.create(attrs, result_data)
173172

174173

175174
def _to_http(
176175
event: AnyCloudEvent,
177176
format: str = converters.TypeStructured,
178-
data_marshaller: types.MarshallerType = None,
179-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
177+
data_marshaller: typing.Optional[types.MarshallerType] = None,
178+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
180179
"""
181180
Returns a tuple of HTTP headers/body dicts representing this Cloud Event.
182181
@@ -196,7 +195,7 @@ def _to_http(
196195
event_handler = _obj_by_version[event["specversion"]]()
197196
for attribute_name in event:
198197
event_handler.Set(attribute_name, event[attribute_name])
199-
event_handler.data = event.data
198+
event_handler.data = event.get_data()
200199

201200
return marshaller.NewDefaultHTTPMarshaller().ToRequest(
202201
event_handler, format, data_marshaller=data_marshaller
@@ -205,8 +204,8 @@ def _to_http(
205204

206205
def to_structured(
207206
event: AnyCloudEvent,
208-
data_marshaller: types.MarshallerType = None,
209-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
207+
data_marshaller: typing.Optional[types.MarshallerType] = None,
208+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
210209
"""
211210
Returns a tuple of HTTP headers/body dicts representing this Cloud Event.
212211
@@ -222,8 +221,8 @@ def to_structured(
222221

223222

224223
def to_binary(
225-
event: AnyCloudEvent, data_marshaller: types.MarshallerType = None
226-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
224+
event: AnyCloudEvent, data_marshaller: typing.Optional[types.MarshallerType] = None
225+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
227226
"""
228227
Returns a tuple of HTTP headers/body dicts representing this Cloud Event.
229228
@@ -287,19 +286,13 @@ def to_dict(event: AnyCloudEvent) -> typing.Dict[str, typing.Any]:
287286
:returns: The canonical dict representation of the event.
288287
"""
289288
result = {attribute_name: event.get(attribute_name) for attribute_name in event}
290-
result["data"] = event.data
289+
result["data"] = event.get_data()
291290
return result
292291

293292

294293
def _json_or_string(
295-
content: typing.Optional[typing.AnyStr],
296-
) -> typing.Optional[
297-
typing.Union[
298-
typing.Dict[typing.Any, typing.Any],
299-
typing.List[typing.Any],
300-
typing.AnyStr,
301-
]
302-
]:
294+
content: typing.Optional[typing.Union[str, bytes]],
295+
) -> typing.Any:
303296
"""
304297
Returns a JSON-decoded dictionary or a list of dictionaries if
305298
a valid JSON string is provided.

cloudevents/http/__init__.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
from cloudevents.http.json_methods import to_json # deprecated
2626

2727
__all__ = [
28-
to_binary,
29-
to_structured,
30-
from_json,
31-
from_http,
32-
from_dict,
33-
CloudEvent,
34-
is_binary,
35-
is_structured,
36-
to_binary_http,
37-
to_structured_http,
38-
to_json,
28+
"to_binary",
29+
"to_structured",
30+
"from_json",
31+
"from_http",
32+
"from_dict",
33+
"CloudEvent",
34+
"is_binary",
35+
"is_structured",
36+
"to_binary_http",
37+
"to_structured_http",
38+
"to_json",
3939
]

cloudevents/http/conversion.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
def from_json(
2525
data: typing.Union[str, bytes],
26-
data_unmarshaller: types.UnmarshallerType = None,
26+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
2727
) -> CloudEvent:
2828
"""
2929
Parses JSON string `data` into a CloudEvent.
@@ -38,8 +38,8 @@ def from_json(
3838

3939
def from_http(
4040
headers: typing.Dict[str, str],
41-
data: typing.Union[str, bytes, None],
42-
data_unmarshaller: types.UnmarshallerType = None,
41+
data: typing.Optional[typing.Union[str, bytes]],
42+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
4343
) -> CloudEvent:
4444
"""
4545
Parses CloudEvent `data` and `headers` into a CloudEvent`.

cloudevents/http/event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def __init__(self, attributes: typing.Dict[str, str], data: typing.Any = None):
8282
def _get_attributes(self) -> typing.Dict[str, typing.Any]:
8383
return self._attributes
8484

85-
def _get_data(self) -> typing.Optional[typing.Any]:
85+
def get_data(self) -> typing.Optional[typing.Any]:
8686
return self.data
8787

8888
def __setitem__(self, key: str, value: typing.Any) -> None:

cloudevents/http/http_methods.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
details="Use cloudevents.conversion.to_binary function instead",
3232
)
3333
def to_binary(
34-
event: AnyCloudEvent, data_marshaller: types.MarshallerType = None
35-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
34+
event: AnyCloudEvent, data_marshaller: typing.Optional[types.MarshallerType] = None
35+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
3636
return _moved_to_binary(event, data_marshaller)
3737

3838

@@ -42,8 +42,8 @@ def to_binary(
4242
)
4343
def to_structured(
4444
event: AnyCloudEvent,
45-
data_marshaller: types.MarshallerType = None,
46-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
45+
data_marshaller: typing.Optional[types.MarshallerType] = None,
46+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
4747
return _moved_to_structured(event, data_marshaller)
4848

4949

@@ -53,21 +53,21 @@ def to_structured(
5353
)
5454
def from_http(
5555
headers: typing.Dict[str, str],
56-
data: typing.Union[str, bytes, None],
57-
data_unmarshaller: types.UnmarshallerType = None,
56+
data: typing.Optional[typing.AnyStr],
57+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
5858
) -> CloudEvent:
5959
return _moved_from_http(headers, data, data_unmarshaller)
6060

6161

6262
@deprecated(deprecated_in="1.0.2", details="Use to_binary function instead")
6363
def to_binary_http(
64-
event: CloudEvent, data_marshaller: types.MarshallerType = None
65-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
64+
event: CloudEvent, data_marshaller: typing.Optional[types.MarshallerType] = None
65+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
6666
return _moved_to_binary(event, data_marshaller)
6767

6868

6969
@deprecated(deprecated_in="1.0.2", details="Use to_structured function instead")
7070
def to_structured_http(
71-
event: CloudEvent, data_marshaller: types.MarshallerType = None
72-
) -> typing.Tuple[dict, typing.Union[bytes, str]]:
71+
event: CloudEvent, data_marshaller: typing.Optional[types.MarshallerType] = None
72+
) -> typing.Tuple[typing.Dict[str, str], bytes]:
7373
return _moved_to_structured(event, data_marshaller)

cloudevents/http/json_methods.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
)
3232
def to_json(
3333
event: AnyCloudEvent,
34-
data_marshaller: types.MarshallerType = None,
35-
) -> typing.Union[str, bytes]:
34+
data_marshaller: typing.Optional[types.MarshallerType] = None,
35+
) -> bytes:
3636
return _moved_to_json(event, data_marshaller)
3737

3838

@@ -42,6 +42,6 @@ def to_json(
4242
)
4343
def from_json(
4444
data: typing.Union[str, bytes],
45-
data_unmarshaller: types.UnmarshallerType = None,
45+
data_unmarshaller: typing.Optional[types.UnmarshallerType] = None,
4646
) -> CloudEvent:
4747
return _moved_from_json(data, data_unmarshaller)

0 commit comments

Comments
 (0)