SoftwareAG / cumulocity-python-api

Python client for the Cumulocity REST API. Created by Global Competency Center IoT
Apache License 2.0
18 stars 13 forks source link

Accessing in-built fragments of managed objects in json/dict form #38

Closed reubenmiller closed 1 year ago

reubenmiller commented 1 year ago

Is there a way to get the full Cumulocity IoT managed object representation related to the ManagedObject python class representation which includes in-built/read-only fragments (e.g. lastUpdated, id, creationTime)?

I'm currently using the to_json() method, however it does not include the fragments mentioned above. Yes I could use the __dict__ method, however that would only include the python model's representation of the fields, e.g. it would have update_time rather than the Cumulocity fragmentlastUpdated.

Looking further there seems to be already some logic which looks at adding back some fragments when constructing the json dictionary, so the in-builts could potentially be added back in there. Below shows that the c8y_IsDevice is re-added into the device when marshalling to a dictionary object.

def to_json(self, only_updated=False) -> dict:
    # (no doc changes)
    object_json = super().to_json(only_updated)
    if not only_updated:
        object_json['c8y_IsDevice'] = {}
    return object_json

Note: The above example was taken from 1.5.x, so the exact implementation has changed slightly but the main point remains.

Another place where this logic could be slotted in could be the to_full_json() method, as the name might be a bit more fitting (at least from a first glance).

Background

I've been using c8yapi in another library that I've been working on to support Robot Framework tests, robotframework-c8y. Under the hood I'm using this library, and I'm mostly returning any objects as a plain dictionary to the user so they can do any followup assertions using standard python objects (and not custom classes where the user has to read through docs).

A dumbed down example would look something like this.

def assert_managed_object_existence(device_id):
    mo = client.inventory.get(device_id)
    return mo.to_json()

The idea is to get a value from Cumulocity, so some assertion, and then return the object to the user, so they could check that the managed object fragments match some expected outcome.

chisou commented 1 year ago

Honestly, the to_json and to_full_json are not intended for regular use. They provide a JSON representation which is used to update an object, hence to_json can be instrumented to only give the updated parts of an object. The __dict__ function won't really help you there as it does not have the same structure as the actual JSON.

You can't do any harm with these, hence they are not hidden. They are also sometimes helpful, especially for tests. I was once thinking about a raw_json property or similar but I didn't go for it as it would just add memory and the use is for corner cases only. The intended audience for this package are users who are not interested in the actual JSON but work with the class' properties.

However, the session handling and everything of this package is really helpful for all kinds of scenarios, so maybe add a feature request?

For your particular case I guess it would be best to recede to the basic REST commands from the API:

def assert_managed_object_existence(device_id):
    return client.get(client.inventory.build_object_path(device_id))

Untested, but something like this should work quite nicely.

reubenmiller commented 1 year ago

Yeah I see your point. I'll have another look at the test interface of my library to support assertions on the lastUpdated/creationTime fragments using the datetime objects. (because in the end having the lastUpdated value as a datetime object is more useful)

If I can't find a nicer interface, I'll consider doing a PR to add a raw_json() method in as I still see it as beneficial to be able to 'reconstruct' the Cumulocity IoT data representation...but maybe that is just me.