Repository: cloudevents/sdk-python

Issue:
__eq__ operator raises attribute error on non-cloudevent values
## Expected Behavior
Given a non-cloudevent value will return false

## Actual Behavior
raises an `AttributeError`

## Steps to Reproduce the Problem

1. create an event
2. compare it to an integer

```python
from cloudevents.http import CloudEvent

attributes = {
    "source": "test",
    "type": "google.cloud.audit.log.v1.written",
}
event = CloudEvent(attributes)
event == 1
```

## Specifications

- Platform: Windows
- Python Version: Python 3.9


Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e1891b..06c9f29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added `.get` accessor for even properties ([#165])
 - Added type information for all event member functions ([#173])
 
+### Fixed
+-  Fixed event `__eq__` operator raising `AttributeError` on non-CloudEvent values ([#172])
+
 ### Changed
 - Code quality and styling tooling is unified and configs compatibility is ensured ([#167])
 - CI configurations updated and added macOS and Windows tests ([#169])
@@ -18,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Removed
 - `docs` folder and related unused tooling ([#168])
 
+
 ## [1.3.0] — 2022-09-07
 ### Added
 - Python 3.9 support ([#144])
@@ -156,4 +160,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [#168]: https://github.com/cloudevents/sdk-python/pull/168
 [#169]: https://github.com/cloudevents/sdk-python/pull/169
 [#170]: https://github.com/cloudevents/sdk-python/pull/170
+[#172]: https://github.com/cloudevents/sdk-python/pull/172
 [#173]: https://github.com/cloudevents/sdk-python/pull/173
diff --git a/cloudevents/http/event.py b/cloudevents/http/event.py
index b4ef41a..ee78cff 100644
--- a/cloudevents/http/event.py
+++ b/cloudevents/http/event.py
@@ -68,7 +68,9 @@ class CloudEvent:
             )
 
     def __eq__(self, other: typing.Any) -> bool:
-        return self.data == other.data and self._attributes == other._attributes
+        if isinstance(other, CloudEvent):
+            return self.data == other.data and self._attributes == other._attributes
+        return False
 
     # Data access is handled via `.data` member
     # Attribute access is managed via Mapping type


Test patch:
diff --git a/cloudevents/tests/test_http_cloudevent.py b/cloudevents/tests/test_http_cloudevent.py
index 3737ea6..fa4bd91 100644
--- a/cloudevents/tests/test_http_cloudevent.py
+++ b/cloudevents/tests/test_http_cloudevent.py
@@ -47,6 +47,18 @@ def your_dummy_data():
     return '{"name":"paul"}'
 
 
+@pytest.fixture()
+def dummy_event(dummy_attributes, my_dummy_data):
+    return CloudEvent(attributes=dummy_attributes, data=my_dummy_data)
+
+
+@pytest.fixture()
+def non_exiting_attribute_name(dummy_event):
+    result = "nonexisting"
+    assert result not in dummy_event
+    return result
+
+
 def test_http_cloudevent_equality(dummy_attributes, my_dummy_data, your_dummy_data):
     data = my_dummy_data
     event1 = CloudEvent(dummy_attributes, data)
@@ -71,6 +83,21 @@ def test_http_cloudevent_equality(dummy_attributes, my_dummy_data, your_dummy_da
     assert event1 != event2 and event3 != event1
 
 
+@pytest.mark.parametrize(
+    "non_cloudevent_value",
+    (
+        1,
+        None,
+        object(),
+        "Hello World",
+    ),
+)
+def test_http_cloudevent_must_not_equal_to_non_cloudevent_value(
+    dummy_event, non_cloudevent_value
+):
+    assert not dummy_event == non_cloudevent_value
+
+
 def test_http_cloudevent_mutates_equality(
     dummy_attributes, my_dummy_data, your_dummy_data
 ):
@@ -145,18 +172,6 @@ def test_none_json_or_string():
     assert _json_or_string(None) is None
 
 
-@pytest.fixture()
-def dummy_event(dummy_attributes, my_dummy_data):
-    return CloudEvent(attributes=dummy_attributes, data=my_dummy_data)
-
-
-@pytest.fixture()
-def non_exiting_attribute_name(dummy_event):
-    result = "nonexisting"
-    assert result not in dummy_event
-    return result
-
-
 def test_get_operation_on_non_existing_attribute_must_not_raise_exception(
     dummy_event, non_exiting_attribute_name
 ):