Home
The pyc8y (Cumulocity Python SDK) module is a pure Python implementation
of Cumulocity IoT's REST interface based on asyncio.
→ Getting started with the Cumulocity Python SDK
REST API Connector
The SDK module provides a convenience wrapper around the standard Cumulocity REST API (see also the OpenAPI documentation).
The CumulocityRestClient class provides the
fundamental wrapping around authentication and basic get, post,
put, delete commands. The CumulocityClient
class is your entrypoint into higher level funct ions, grouped by
contexts like inventory, users, and measurements. Each of these
contexts is documented in detail within the
main-api-classes section.
The DeviceRegistryClient class provides an additional entry point for devices, wrapping the entire bootstrap mechanism. See also the Device integration documentation at Cumulocity.
Application Helpers
The SDK is designed to be particularly useful for developing Cumulocity microservices. For this, the module provides two helper classes that take care of microservice specific authentication.
The SimpleCumulocityApp class should be used for single tenant microservices. It automatically reads the microservice's environment to determines the microservice access credentials.
The MultiTenantCumulocityApp class
should be used for multi-tenant microservices which need to handle requests
for arbitrary Cumulocity tenants. It reads the microservice's environment to
determine the necessary bootstrap credentials and provides additional
functions to dynamically obtain CumulocityClient instances for specific
tenants.
For interactive use (e.g. in Jupyter Notebooks) the get_client function can be used to obtain a ready to use CumulocityClient instance. It honors from standard Cumulocity environment variables for not explicity provided connection details, and falls back to interactive prompts for anything still missing.
Main API Classes
The pyc8y module's main API classes provide access to various contexts within the Cumulocity REST API. Use it to read existing data or modify in bulk.
See also the Object model section for object creation and object-oriented access in general.
Querying the database
The main API classes can be used to read individual objects by their respective key, usually the Cumulocity object ID:
some_object = await c8y.inventory.get("123456")
some_user = await c8y.users.get("mini_me")
some_id = await c8y.identity.get("9876543210", "c8y_Serial")
To access entire collections most APIs provide convenient query functions which reflect the respective API's capabilities:
The get_all function provides the query results as a list, the
feature-equivalent select function works as a generator for streaming
access. Both functions perform automatic paging if necessary.
The returned objects are instances of the Object model and the functions are properly typed, so the results can conveniently used directly as-is:
async for event in c8y.events.select(source="123456", max_age="1d", limit=100):
print(f"Event: #{event.id} {event.time}, Type {event.type}")
Flexible query parameters
While all query functions are functionally equivalent, each API provides a specialized version which reflects the respective REST API's capabilities. For example, the Inventory API provides OData-like queries which for example are not supported for the Event API or Operation API.
All potential filters available through code completion. They match the
REST API's naming and function - however, the names have been renamed to
match Python's snake_case naming convention (e.g. date_from instead
of dateFrom). The original names can always be used as a fallback, though.
Additionally, convenient alternatives are provided for date-like query
parameters:
c8y.events.get_all(source="123456", dateFrom="2025-01-01") # original
c8y.events.get_all(source="123456", date_from="2025-01-01") # Pythonic
c8y.events.get_all(source="123456", after="2025-01-01") # alternative
c8y.events.get_all(source="123456", max_age="1d") # convenience helper
datetime objects, ISO
date/time strings as well as "now" as a convenience alternative.
As a fallback, all query functions support the direct definition of a REST API expression:
High-performance throughput
The SDK features an async first design with IO concurrency and high
throughput in mind. Hence, all query functions can be executed in
concurrent mode via the workers parameter:
This will internally fetch pages concurrently and return the complete set. Results will be returned in random order (by completion), unless an ordering parameter is specified:
all_events = await c8y.events.get_all(
source="12345",
ascending=True, # or: revert=True
limit=None,
page_size=100,
workers=20)
Especially when working on large data sets, the full result object's
JSON is usually obsolete. To directly forfeit the JSON data and only
process relevant fields, the as_values parameter can be used on all
query functions:
async for values in c8y.inventory.select(
type="MyDevice",
as_values=("id", ("name", "undefined"), "lastUpdated")):
print(f"#{values[0]} '{values[1]}', last updated: {values[2]}")
Object manipulation and bulk processing
The main API classes can also be used to directly manipulate the database without the need to pull object instances:
# add a new fragment to a known set of inventory objects:
new_fragment_json = {"my_AdditionalFragment": {"essential": True}}
await c8y.inventory.update(new_fragment_json, "123456", "1238957", "12399192")
Similar functions exist to create and delete objects in bulk. Each API
class additionally provides access to API specific functionality, for example
to assign group memberships, define child devices, reset user passwords, ...
Like the query functions, all bulk operations feature a workers parameter to
enable concurrency and maximize throughput:
Because all these functions are defined as async, the bulk operations can
easily be combined using standard asyncio means:
# clean up my data
object_events = await c8y.events.get_all(source=my_object.id, limit=None, workers=20, as_values="id")
object_alarms = await c8y.alarms.get_all(source=my_object.id, limit=None, workers=20, as_values="id")
await asyncio.gather(
c8y.events.delete( *object_events, workers=20),
c8y.alarms.delete(*object_alarms, workers=20),
)
await c8y.inventory.delete(my_object.id)
→ Inventory
→ DeviceInventory
→ Identity
→ Binaries
→ Measurements
→ Events
→ Alarms
→ Users
→ UserGroups
→ InventoryRoles
→ Subscriptions
→ Tokens
→ Operations
→ BulkOperations
→ Applications
→ TenantOptions
→ AuditRecords
Object Models
The Cumulocity Python SDK's object model provides object-oriented access to the Cumulocity REST API. Use it to create and modify single objects within the Database.
Object-oriented database access
The object model define functions for direct, object-oriented database access:
# Create an object in the database and use it
obj = ManagedObject(
connection,
name="MyObject",
type="my_CustomType"
)
obj = await obj.create()
# Update the object and write changes to the database
obj.type = "my_ChangedType"
await obj.update()
# Remove the previously created object from the database
await obj.delete()
Note: These objects can also be used directly within the Main API classes to modify data in bulk.
Direct JSON data access
The object model instances are essentially enriched JSON-like objects and can be used like standard nested dictionaries:
the_value = obj["my_Fragment"]["value"] # read nested value
obj["my_Fragment"]["value"] = new_value # set nested value
For convenience the SDK supports dot-notation for the [] operator:
the_value = obj["my_Fragment.value"] # read nested value
obj["my_Fragment.value"] = new_Value # set nested value
Likewise, the .get and .set functions:
the_value = obj.get("my_Fragment.value", default=42) # read nested value
obj.set("my_Fragment.value", 42) # set a nested value
which also protect against missing keys along the path.
Many Cumulocity objects define standard properties, e.g. id, type,
name and creationTime. These fields can additionally also read
(and set if allowed) through explicit class properties:
# Most objects have an id, name, and type
print(f"Object details: #{obj.id}, '{obj.name}' ({obj.type})")
# Date-like properties can be read as strings (actual JSON value) or datetime (parsed)
total_minutes = int((datetime.now(timezone.utc) - obj.creation_datetime).total_seconds() / 60)
print(f"Object creation: {obj.creation_time} ({total_minutes} minutes ago)")
These properties use Pythonic snake_case naming to integrate nicely into
Python code. The original data is always available through direct access,
e.g. obj["creationTime"].
→ ManagedObject
→ Device
→ DeviceGroup
→ ExternalId
→ Binary
→ Measurement
→ Event
→ Alarm
→ Series
→ Subscription
→ Availability
→ User
→ CurrentUser
→ TfaSettings
→ UserGroup
→ Permission
→ ReadPermission
→ WritePermission
→ AnyPermission
→ Operation
→ BulkOperation
→ Application
→ ApplicationSubscription
→ TenantOption
→ AuditRecord
→ Change
Measurement Additions
The Cumulocity Python SDK's measurements API (see also classes
Measurements and
Measurement) includes the following
additions to allow easy creation of standard measurement values
including units.
Effectively, each of the value classes represent a value fragment, e.g.
Celsius:
These values can easily be combined, e.g. when constructing a measurement:
m = Measurement(
type='cx_LevelMeasurement', source=device_id, time='now',
cx_Levels={
'oil': Liters(8.4),
'gas': Liters(223.18),
'h2o': Liters(1.2),
'bat': Percentage(85)
})
→ Units
→ Celsius
→ Centimeters
→ Count
→ CubicMeters
→ Grams
→ Kelvin
→ Kilograms
→ Liters
→ Meters
→ Percent
→ Value