Code Monkey home page Code Monkey logo

cumulocity-python-api's Introduction

GitHub GitHub release (latest by date) GitHub Release Date ReadTheDocs

cumulocity-python-api

This project is a Python client for the Cumulocity REST API to make it easier to develop programs, scripts, device agents or microservices in Python.

See also the documentation on Read the Docs.

Installation

Prerequisites

Before installing the module (or any module for that matter) consider creating a virtual environment for your project. This is generally preferred over installing modules and dependencies globally:

cd <project-root>
python3 -m venv venv
source venv/bin/activate

Installation from PyPI

The recommended way is to install the lastest distribution package directly from the Python Package Index (PyPI). You can either add c8y_api as a dependency to your project using setup.cfg, or install it manually:

pip install c8y_api

Installation using pip

Releases are also archived within the GitHub releases page. The module is released as standard Python wheel (.whl file). It can be downloaded and installed using pip using the following command:

pip install <release wheel file>

Like installing from PyPI, this will install all necessary dependencies automatically. For your reference, the module's dependencies are also listed in file requirements.txt.

Manual installation

Alternatively, you can clone the repository. The module sources can be used directly within your Python 3 project. Simply copy the c8y_api folder to your sources root and install the requirements by running the following command:

pip3 install -r requirements.txt

The requirements.txt file is part of the sources.

If the c8y_api folder is in your sources root folder all imports should work right away. Alternatively you can add c8y_api to your PYHTONPATH:

export PYTHONPATH=<project-root>/c8y_api; $PYTHONPATH

Licensing

This project is licensed under the Apache 2.0 license - see https://www.apache.org/licenses/LICENSE-2.0


These tools are provided as-is and without warranty or support. They do not constitute part of the Software AG product suite. Users are free to use, fork and modify them, subject to the license agreement. While Software AG welcomes contributions, we cannot guarantee to include every contribution in the master project.


You can find additional information in the Software AG TECHcommunity. There is also an introductory article (Getting started with the Cumulocity Python API) available.

Contact us at TECHcommunity if you have any questions.

cumulocity-python-api's People

Contributors

cguether avatar chisou avatar dependabot[bot] avatar haraldmeyer avatar techcommunity avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

cumulocity-python-api's Issues

Improve caching/usability for multi-tenant scenarios

Within a multitenant environment and you want to get the tenant specific instance you are supposed to invoke
.get_tenant_instance(id) or CumulocityApp(id) (with the tenant ID). Subsequent calls to this will result in multiple bootstrap instances being created. This is not ideal.

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

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.

DeviceGroup class should remove method .add_child_device(), which is confusing and also leads to incorrect behaviors

Method .add_child_device() should be removed from DeviceGroup, as DeviceGroup uses .add_child_asset() to associate a device with the group. Using DeviceGroup.add_child_device() for this purpose is very confusing and the results seems also incorrect as stated in below issue 1.

Issue 1.

  • when use DeviceGroup.add_child_device(), the added device is not shown in the group from the tenant. However, deleting the group and its assets will also delete the device. This is very confusing.

Issue 2.

  • After execute DeviceGroup.add_child_asset(), the attributes of the DeviceGroup .child_assets are still empty, which seems not right.

Device Inventory API fails when name contains single apostrophe symbol

I would like to retrieve the device ID for a device name containing a single apostrophe (').

Running the following results in a raised exception:

>>> from c8y_api.app import SimpleCumulocityApp
>>> app = SimpleCumulocityApp()
>>> app.device_inventory.get_all(name="Sam's Device")
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/sam/dev/cumulocity-python-api/c8y_api/model/inventory.py", line 194, in get_all
    def get_all(self, type: str = None, name: str = None, owner: str = None,   # noqa (type, parameters)
  File "/home/sam/dev/cumulocity-python-api/c8y_api/model/_base.py", line 522, in _iterate
    def _iterate(self, base_query, limit, parse_func):
  File "/home/sam/dev/cumulocity-python-api/c8y_api/model/_base.py", line 518, in _get_page
    def _get_page(self, base_query, page_number):
  File "/home/sam/dev/cumulocity-python-api/c8y_api/_base_api.py", line 101, in get
    def get(self, resource: str, params: dict = None, accept: str = None, ordered: bool = False) -> dict:
ValueError: Unable to perform GET request. Status: 422 Response:
{"error":"inventory/Invalid Data","message":"Find by filter query failed : Query 'name eq 'Sam's Device'' could not be understood. Please try again.","info":"https://www.cumulocity.com/guides/reference-guide/#a-name-error-reporting-a-error-reporting"}

If I apply OData conversion rules my end (i.e. use two apostrophes), the query works, like so:

>>> app.device_inventory.get_all(name="Sam''s Device")
[<c8y_api.model.manag...f1d1d0550>]

However I think this ought to be performed by c8y_api prior to sending a request.

Alarm with None time attribute returns an error

Following alarm results in an exception:
alarm = Alarm(None, 'sag_DobotAlarm', None, deviceId, text, Alarm.Status.ACTIVE, Alarm.Severity.MAJOR)

File "C:\Users\APES\AppData\Local\Programs\Python\Python310\lib\site-packages\c8y_api\model\alarms.py", line 333, in create
super()._create(Alarm.to_full_json, *alarms)
File "C:\Users\APES\AppData\Local\Programs\Python\Python310\lib\site-packages\c8y_api\model_base.py", line 494, in _create
self.c8y.post(self.resource, json=jsonify_func(o), accept=None)
File "C:\Users\APES\AppData\Local\Programs\Python\Python310\lib\site-packages\c8y_api_base_api.py", line 190, in post
raise ValueError(f"Unable to perform POST request. Status: {r.status_code} Response:\n" + r.text)
ValueError: Unable to perform POST request. Status: 422 Response:
{"error":"undefined/validationError","message":"Following mandatory fields should be included: time","info":"https://www.cumulocity.com/guides/reference-guide/#a-name-error-reporting-a-error-reporting"}

Using now() is working fine:
alarm = Alarm(None, 'sag_DobotAlarm', datetime.now().isoformat(), deviceId, text, Alarm.Status.ACTIVE, Alarm.Severity.MAJOR)

Notification 2 API SSL Error

c8y.notification2_tokens.build_websocket_uri(token) always returns wss://... instead of ws:// what is needed when running it inside of a cumulocity hosted microservice. What i do now is using my own Listener and creating the uri like this: f"{c8y.base_url.replace('http','ws')}/notification2/consumer/?token={token}"

mo.update() seems not working when fragment is edited by [] operator?

Hi team,

Thank you for making such an awesome Python library, I really appreciate it!

Just a quick question:
I am trying to update a device’s fragment using mo.update() as below.
I can print mo.fragments and confirm the fragments have been changed, however, this change has not be updated to the database.

    def test1():
      mo = c8y.device_inventory.get(id=123)
      mo.fragments['c8y_Hardware']['serialNumber'] ='abc'
      print(mo.fragments)
      mo.update()

On the other hand, the below code works as expected.
The fragments has been changed by add_fragment function, and successfully updated to the database.

 def test2():
    mo = c8y.device_inventory.get(id=123)
    mo.add_fragment('c8y_Hardware',serialNumber='abc')
    print(mo.fragments)
    mo.update()

So it seems like [] operator can edit fragments on the managed object, but mo.update() will not update the fragments on the device?

Is it possible if you could tell me what I’ve done something wrong on test1()?

Or is it because the API can not tell the difference on the fragments when [] operator is used?

My c8y-api version is 1.9.1.

Much appreciated!

Regards
Henry

Exception when calling DeviceInventory.get_all

Function DeviceInventory.get_all() delegates its call to its parent, Inventory.get_all(), which in turn calls self.select(). That results in a parameter missmatch as self refers to an instance of DeviceInventory while the function parameters match with Inventory.select()

Replacing line 784 in module/inventory.py with
return [ x for x in self.select( type = type, name = name, page_size = page_size ) ]
resolved the issue for me

SimpleCumulocityApp default auth not working then running as microservice on C8Y

Then SimpleCumulocityApp without using "get_user_instance" and just use the SimpleCumulocityApp directly will get a 401 then runing as a microservice on C8Y, if running as a local docker container the same call works.
The python api is only using the C8Y_USER and C8Y_PASSWORD and that works localy but then deployed the C8Y_TENANT is also needed for the username the call should be: auth=HTTPBasicAuth(f'{C8Y_TENANT}/{C8Y_USER}', f'{C8Y_PASSWORD}') in the SimpleCumulocityApp init function altrnatively not set the Auth value at all, since it is set correctly in the CumulocityRestApi init function.

Query parameter c8y.inventory.select is not working

I would like to send a query to the inventory api:

filter_01 = f"$filter=applicationId eq '{app_id}' and name eq '{APPLICATION_NAME}'"
managed_objects_app = c8y.inventory.select(query=filter_01)

When I debug the url I don't see that it is considered:
DEBUG:urllib3.connectionpool:https://SOME_TENANT.cumulocity.com:443 "GET /inventory/managedObjects?pageSize=1000&currentPage=1 HTTP/1.1" 200 None

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.