question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Consider a more Pythonic API

See original GitHub issue

Hi all, first: thanks for providing this library. It’s going to be really useful to have a standard way to work with CloudEvents from Python.

I took a preliminary look at it and my first impression was that the interface provided by this library isn’t very “Pythonic”, meaning that it doesn’t provide an interface in a way that (I believe) would be expected by most Python developers. There are some small things (like using method names like FromRequest instead of from_request, using New in class/method names) but also larger things (like the use of method chaining, setters/getters, etc.)

I’m hoping that since this project is still in alpha, there’s still time to improve the interface here (and that y’all are open to suggestions). I’ll try to provide some examples below of what I’m talking about but I’m happy to clarify if anything’s unclear. I’m also not intimately familiar with the CloudEvents spec, so I may have some misunderstandings as well.

Parsing upstream Event from HTTP Request

The current example for turning an HTTP request into an event:

import io

from cloudevents.sdk.event import v02
from cloudevents.sdk import marshaller

m = marshaller.NewDefaultHTTPMarshaller()
event = m.FromRequest(
    v02.Event(),
    {
        "content-type": "application/cloudevents+json",
        "ce-specversion": "0.2",
        "ce-time": "2018-10-23T12:28:22.4579346Z",
        "ce-id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
        "ce-source": "<source-url>",
        "ce-type": "word.found.name",
    },
    io.BytesIO(b"this is where your CloudEvent data"), 
    lambda x: x.read()
)
  • This makes the assumption that the user is only interested in supporting a single spec, but I think it’s possible that either they would want to support multiple versions (and automatically detect which one is being used), or be able to upgrade to newer versions of the spec (and this module) without having to change their code.
  • There’s only one type of marshaller provided by the library right now, HTTPMarshaller, and if it’s the default maybe we should just get it by default when creating an Event, instead of making the user initialize it every time?
  • The data_unmarshaller is maybe not necessary? We’re taking a bytestring, turning it into a BytesIO object to pass as data, and then using data_unmarshaller to turn it back into a bytestring. Why not just have the user do any data unmarshalling themselves before constructing the event?

Instead, consider:

from cloudevents.sdk import Event

event = Event(
    headers={
        "content-type": "application/cloudevents+json",
        "ce-specversion": "0.2",
        "ce-time": "2018-10-23T12:28:22.4579346Z",
        "ce-id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
        "ce-source": "<source-url>",
        "ce-type": "word.found.name",
    },
    body=b"this is where your CloudEvent data",
)
  • This initializes a generic Event, detects which spec it matches, and gives back a corresponding Event subclass. (If the user knew which spec they were supporting, instead of constructing an Event they could construct a v02.Event and skip the event detection)
  • The HTTPMarshaller is used by default. This also helps avoid issues like #12.
  • The data_unmarshaller field is removed (or at the very least, optional)

Creating a minimal CloudEvent

The provided example is:

from cloudevents.sdk.event import v01

event = (
    v01.Event().
    SetContentType("application/json").
    SetData('{"name":"john"}').
    SetEventID("my-id").
    SetSource("from-galaxy-far-far-away").
    SetEventTime("tomorrow").
    SetEventType("cloudevent.greet.you")
)
  • Similar to above, the user has to specify the exact event type they’re expecting
  • Method chaining / fluent API: while this pattern is used by some Python libraries (pandas, various ORMs) it’s not typical for the majority of Python libraries and generally not recommended.

Instead, consider the following based on the same Event class proposed above:

from cloudevents.sdk import Event

event = Event(
    data=b'{"name":"john"}',
    content_type="application/json",
    event_id="my-id",
    event_time="tomorrow",
    event_type="cloudevent.greet.you",
    source="from-galaxy-far-far-away",
)
  • The spec is automatically determined, but the user can also use a specific subclass if they prefer.
  • The constructor uses keyword arguments, so that an Event object can be created with a single method call. If the user needs to modify the event after initialization, they can act on the attributes directly (e.g. event.event_time = "yesterday").

Getting event attributes

Currently, after creating an event, if the user wanted to get one of the fields such as the event time, the user would have to do something like:

event = ...
data = event.ce__eventTime.value
  • The ce-prefix is a bit unexpected and the double-underscore is unconventional
  • Having to call .value to get the value is also unexpected

Instead, consider something like:

event = ...
data = event.event_time
  • No prefix or double underscore
  • Access the attribute value directly

Final thoughts

I realize this is proposing a pretty big overhaul of the current interface, but I think doing so would probably go a long way to lend towards the usability and maintainability of this library, and it’d be better to do it sooner than later. I’m happy to help out here, including designing/implementing these changes and helping maintain them afterwards, if it makes sense. Thanks!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:6
  • Comments:13 (9 by maintainers)

github_iconTop GitHub Comments

3reactions
evankandersoncommented, Jun 27, 2020

Coming here from #47 , I think there’s been a bit of a misunderstanding (either by myself or by one of the other authors here). Looking at the new CloudEvent class, it looks like it assumes that the payload of a CloudEvent is necessarily JSON.

According to my reading of the spec, the CloudEvent payload is not always JSON:

This specification does not place any restriction on the type of this information. It is encoded into a media format which is specified by the datacontenttype attribute (e.g. application/json), and adheres to the dataschema format when those respective attributes are present.

Furthermore, when encoding a CloudEvent with non-JSON data, the data may need to be converted to a base-64 encoded string (if Binary), or serialized as a JSON string (if not). I suspect that the current signature of “CloudEvent” in #47 and mentioned in https://github.com/cloudevents/sdk-python/issues/26#issuecomment-650322206 probably won’t work for e.g. datacontenttype="image/png", because the type of data in the constructor is typing.Union[dict, None], rather than typing.Any (or some sort of type.HasStrMethod).

1reaction
dicommented, Apr 2, 2020

Seems like there is a similar discussion on the Java SDK here: https://github.com/cloudevents/sdk-java/pull/108

(cc @slinkydeveloper)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Python & APIs: A Winning Combo for Reading Public Data
In this tutorial you'll focus more on the high-level APIs that communicate ... The request above returns 200 , so you can consider...
Read more >
Best Practices to Make Your Python More Readable
The best Python code is clear and readable. What practices should you avoid and follow, and how do you deal ... Here's what...
Read more >
5 Ways to Write More Pythonic Code - Built In
Coding is an art form. Here's how to elevate the quality and legibility of your Python code.
Read more >
Python API Tutorial: Getting Started with APIs - Dataquest
In this Python API tutorial, learn about APIs by requesting and analyzing data from the international space station using Python.
Read more >
Absolute beginners guide to slaying APIs using Python
Lets consider this analogy, you are the requester, the kitchen is a ... To interact with an API, specifically a web API in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found