Build Entities#
This chapter explains how to build NGSI-LD compliant entities.
Cookbook shows realistic examples - taken from the Smart Data Models Initiative - built with ngsildclient.
Create an entity#
A NGSI-LD entity is characterized by :
a unique and fully qualified identifier
the type the entity belongs to
a context
1. The preferred way#
Provide :
the type the entity belongs to
a fully qualified identifier
from ngsilclient import Entity
entity = Entity("AirQualityObserved", "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z")
Let’s display our newly created entity
entity.pprint()
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved"
}
Note that the library has assigned a default context.
To simplify the code we can omit the scheme and namespace identifier.
entity = Entity("AirQualityObserved", "AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z")
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
1. Alternate way#
One could provide the qualified identifier and let the library infer the type.
entity = Entity("urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z")
Which can be written.
entity = Entity("AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z")
Specify a context#
1. Using the context property#
from ngsilclient import Entity
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.context.append("https://github.com/smart-data-models/dataModel.Environment/blob/master/context.jsonld")
2. Using the constructor#
from ngsilclient import Entity, CORE_CONTEXT
ctx = [ "https://github.com/smart-data-models/dataModel.Environment/blob/master/context.jsonld",
CORE_CONTEXT ]
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z", ctx=ctx)
Add properties#
Here we talk about properties in the broad sense, including relationships.
Primitives#
property |
primitive |
|---|---|
Property |
prop() |
GeoProperty |
gprop() |
Temporal Property |
tprop() |
Relationship |
rel() |
from ngsilclient import Entity
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.prop("CO", 500).prop("NO", 45).prop("NO2", 69)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved",
"CO": {
"type": "Property",
"value": 500
},
"NO": {
"type": "Property",
"value": 45
},
"NO2": {
"type": "Property",
"value": 69
}
}
Metadata#
Corresponding arguments in lower case.
metadata |
argument |
|---|---|
unitCode |
unitcode |
datasetId |
datasetid |
observedAt |
observedat |
unitCode#
entity.prop("SO2", 11, unitcode="GP") # milligram per cubic metre (UNECE/CEFACT)
datasetId#
entity.prop("SO2", 11, datasetid="dataset:01") # urn prefix omitted
observedAt#
from datetime import datetime, timezone
entity.prop("SO2", 11, observedat=datetime(2016, 3, 15, 11, tzinfo=timezone.utc))
# Alternatively one could pass directly an ISO8601 string
# entity.prop("SO2", 11, observedat="2016-03-15T11:00:00Z")
from datetime import datetime
from zoneinfo import ZoneInfo
CET = ZoneInfo("CET") # UTC+1
entity.prop("SO2", 11, observedat=datetime(2016, 3, 15, 12, tzinfo=CET))
# Alternatively one could pass directly an ISO8601 string
# entity.prop("SO2", 11, observedat="2016-03-15T11:00:00Z")
For the sake of example let’s rewrite our entity.
from ngsildclient import Entity, Auto
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.prop("CO", 500, unitcode="GP", observedat=Auto)
entity.prop("NO", 45, unitcode="GP", observedat=Auto)
entity.prop("NO2", 69, unitcode="GP", observedat=Auto)
entity.prop("SO2", 11, unitcode="GP", observedat=Auto)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved",
"CO": {
"type": "Property",
"value": 500,
"unitCode": "GP",
"observedAt": "2016-03-15T11:00:00Z"
},
"NO": {
"type": "Property",
"value": 45,
"unitCode": "GP",
"observedAt": "2016-03-15T11:00:00Z"
},
"NO2": {
"type": "Property",
"value": 69,
"unitCode": "GP",
"observedAt": "2016-03-15T11:00:00Z"
},
"SO2": {
"type": "Property",
"value": 11,
"unitCode": "GP",
"observedAt": "2016-03-15T11:00:00Z"
}
}
User data#
entity.prop("NOx", 119, userdata={"accuracy": 0.95})
"NOx": {
"type": "Property",
"value": 119,
"accuracy": 0.95
}
Property#
entity.prop("temperature", 22.5)
entity.prop("description", [
"https://example.org/concept/clay",
"https://datamodel.org/example/clay"]
}
entity.prop("description", "Corn farm")
entity.prop("description", "Corn farm (organic)", escape=True)
GeoProperty#
from geojson import Point
from ngsildclient import Entity
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.gprop("location", Point((-3.703790, 40.416775))) # Madrid
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved",
"location": {
"type": "Property",
"value": {
"type": "Point",
"coordinates": [
-3.70379,
40.416775
]
}
}
}
entity.gprop("location", (40.416775, -3.703790)) # Madrid
The loc() alias can be used to set this very common location GeoProperty.
entity.loc((40.416775, -3.703790)) # Madrid
Temporal Property#
The Temporal Property accepts values of following types : datetime, date and time, or ISO8601 string representations of these latter.
from ngsildclient import Entity
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.tprop("dateObserved", "2016-03-15T11:00:00Z")
from ngsildclient import Entity, Auto
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.tprop("dateObserved", Auto) # We could omit Auto as it's the default
Note
Auto means the cached datetime if any else defaults to the current datetime utcnow().Auto is the default value for the tprop() primitive.The obs() alias can be used to set this very common dateObserved Temporal Property.
entity.obs() # use a cached datetime if any, else current datetime
Relationship#
The Relationship Property points to one or many JSON-LD objects.
from ngsildclient import Entity
entity = Entity("Vehicle", "A4567")
entity.rel("isParked", "OffStreetParking:Downtown1", observedat="2017-07-29T12:00:04Z")
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:Vehicle:A4567",
"type": "Vehicle",
"isParked": {
"type": "Relationship",
"object": "urn:ngsi-ld:OffStreetParking:Downtown1",
"observedAt": "2017-07-29T12:00:04Z"
}
}
Import the Rel Enum to access well-known relationship names, such as observedBy or hasPart.
Implement nested properties#
Sometimes properties are composed of properties.
Single level#
from ngsildclient import Entity, NESTED
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.prop("NO2", 22, unitcode="GP").prop("qc", "checked", NESTED)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved",
"NO2": {
"type": "Property",
"value": 22,
"unitCode": "GP",
"qc": {
"type": "Property",
"value": "checked"
}
}
}
from ngsildclient import Entity, NESTED, Rel
room = Entity("Room", "01")
room.prop("temperature", 17).rel(Rel.OBSERVED_BY, "Sensor:01", NESTED)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:Room:01",
"type": "Room",
"temperature": {
"type": "Property",
"value": 17,
"observedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Sensor:01"
}
}
}
Multilevel#
You can chain nested properties in order to obtain several nesting levels.
from ngsildclient import Entity, NESTED
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.prop("NO2", 22, unitcode="GP").prop("qc", "checked", NESTED).prop("status", "discarded", NESTED)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z",
"type": "AirQualityObserved",
"NO2": {
"type": "Property",
"value": 22,
"unitCode": "GP",
"qc": {
"type": "Property",
"value": "checked",
"status": {
"type": "Property",
"value": "discarded"
}
}
}
}
Anchoring#
from datetime import datetime
from ngsildclient import Entity
parking = Entity("OffStreetParking", "Downtown1")
parking.prop("availableSpotNumber", 121, observedat=datetime(2017, 7, 29, 12, 5, 2).anchor()
parking.prop("reliability", 0.7).rel("providedBy", "Camera:C1").unanchor()
parking.prop("description", "Municipal car park located near the Trindade metro station and the Town Hall")
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:OffStreetParking:Downtown1",
"type": "OffStreetParking",
"availableSpotNumber": {
"type": "Property",
"value": 121,
"observedAt": "2017-07-29T12:05:02Z",
"reliability": {
"type": "Property",
"value": 0.7
},
"providedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Camera:C1"
}
},
"description": {
"type": "Property",
"value": "Municipal car park located near the Trindade metro station and the Town Hall"
}
}
Update an entity#
It provides obviously all the native dictionary staff but also :
the prop(), gprop(), tprop() and rel() primitives quite equivalent to those provided by the Entity
a dot notation to access fields, i.e. room[“temperature.value”]
Let’s consider the following example.
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:Room:01",
"type": "Room",
"temperature": {
"type": "Property",
"value": 22.5,
"observedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Sensor:01"
}
},
"pressure": {
"type": "Property",
"value": 938.8
}
}
Update a member#
id, type and context.from ngsildclient import Entity
room.id = "urn:ngsi-ld:Room:02"
Update a value#
from ngsildclient import Entity
room["temperature.value"] += 0.2
Add metadata or userdata#
Use the same method.
from ngsildclient import Entity
room["temperature.unitCode"] = "CEL"
Remove any part of the Entity#
It applies to properties as well.
from ngsildclient import Entity
del room["temperature.unitCode"]
Update a property#
To update an Entity’s property the easiest way is to override it.
from ngsildclient import Entity
room.prop("pressure", 938.7, unitcode="A97")
Add a nested property#
from ngsildclient import Entity
room["temperature"].prop("qc", "checked")
Add a multilevel nested property#
from ngsildclient import Entity
room["temperature"].prop("qc", "checked").prop("status", "discarded")
Display an entity#
Import/Export#
Dictionary#
Suppose we have this dictionary.
payload = {
"type": "Room",
"id": "urn:ngsi-ld:Room:01",
"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
"temperature": {"type": "Property", "value": 22.5}
}
id, type and @context are mandatory.from ngsildclient import Entity
room = Entity.from_dict(payload)
The opposite operation converts your entity to a dictionary.
from ngsildclient import Entity
payload = room.to_dict()
File#
Load/Save a single entity#
Import and Export from/to a file is a very useful feature that allows :
backup : just restore an entity you’ve previously saved
testing : store an expected result for further comparison
sharing : elaborate with others about modeling
experimenting : load an example from the Smart Data Models Initiative and play around
contributing : propose a NGSI-LD example to the Smart Data Models Initiative
We can load a local file.
from ngsildclient import Entity, SmartDataModels
room = Entity.load("/tmp/room1.jsonld")
And save an entity to a file.
from ngsildclient import Entity, SmartDataModels
room.save("/tmp/room2.jsonld")
We can load a remote file through HTTP.
from ngsildclient import Entity
battery = Entity.load("https://github.com/smart-data-models/dataModel.Battery/raw/master/Battery/examples/example-normalized.jsonld")
Load sample entities#
For convenience some datamodel example URLs of the Smart Data Models Initiative are made available.
from ngsildclient import Entity, SmartDataModels
beach = Entity.load(SmartDataModels.SmartCities.PointOfInterest.Beach)
Batch Import#
from ngsildclient import Entity
rooms: list[Entity] = Entity.load("/tmp/rooms_all.jsonld")
Note
One could use explicitly the load_batch() method that expects a JSON array.
Batch Export#
from ngsildclient import Entity
rooms = [Entity("Room", "Room1"), Entity("Room", "Room2")]
Entity.save_batch(rooms, "/tmp/rooms_all.jsonld")
Utils#
ISO8601#
In NGSI-LD entities dates, times and datetimes are represented as ISO8601 strings.
The iso8601 module provides you with functions to convert from Python types to ISO8601 :
from_date()
from_time()
from_datetime()
utcnow() to get the current datetime
Note that this is not needed for the tprop() primitive and observedat argument that accepts Python date types, calling these functions for you.
from datetime import datetime
from ngsildclient import iso8601, TZ_CET
dt = datetime(2022, 3, 10, 17, 49, tzinfo=TZ_CET)
iso8601.from_datetime(dt) # '2022-03-10T16:49:00Z'
Short UUID#
A UUID may be useful in some cases to create a unique Entity identifier.
But a long dash-separated string is not always suitable. The short UUID string will be 22 characters long, base64-encoded, with padding characters removed. The encoding uses the urlsafe alphabet with a slightly difference. The dash character (often used as a NGSI field separator) is replaced by the tilde character.
from ngsildclient import Entity, shortuuid
dt = datetime(2022, 3, 10, 17, 49, tzinfo=TZ_CET)
crop = Entity("AgriCrop", shortuuid())
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AgriCrop:ldoRJQMZSaaKoWn9g_JR~g",
"type": "AgriCrop"
}
Helpers#
Helper functions can help building complex data structures frequently used in NGSI-LD entities.
Using helper functions :
enforces a well-constructed consistent structure
guides you through the different options thanks to IDE autocompletion
The code may look quite long at first sight but is mainly generated by the IDE.
PostalAddress#
PostalAddress as defined by Schema.org.
from ngsildclient import Entity, PostalAddressBuilder
entity = Entity("WeatherObserved", "Spain-Valladolid-2016-11-30T07:00:00.00Z")
entity.prop("address",
PostalAddressBuilder()
.street("C/ La Pereda 14")
.locality("Santander")
.region("Cantabria")
.country("Spain")
.build())
The addr() alias can be used to set this very common address Property.
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:WeatherObserved:Spain-Valladolid-2016-11-30T07:00:00.00Z",
"type": "WeatherObserved",
"address": {
"type": "Property",
"value": {
"streetAddress": "C/ La Pereda 14",
"addressLocality": "Santander",
"addressRegion": "Cantabria",
"addressCountry": "Spain"
}
}
}
OpeningHours#
OpeningHoursSpecification as defined by Schema.org.
from ngsildclient import Entity, OpeningHours
openinghours = OpeningHoursBuilder()
.businessdays("10:00", "17:30")
.saturday("10:00", "14:00")
.build()
library = Entity("Library", "Ireland-Shannon-PublicLibrary")
library.prop("openingHours", openinghours)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:Library:Ireland-Shannon-PublicLibrary",
"type": "Library",
"openingHours": {
"type": "Property",
"value": [
{
"opens": "10:00",
"closes": "17:30",
"dayOfWeek": "Monday"
},
{
"opens": "10:00",
"closes": "17:30",
"dayOfWeek": "Tuesday"
},
{
"opens": "10:00",
"closes": "17:30",
"dayOfWeek": "Wednesday"
},
{
"opens": "10:00",
"closes": "17:30",
"dayOfWeek": "Thursday"
},
{
"opens": "10:00",
"closes": "17:30",
"dayOfWeek": "Friday"
},
{
"opens": "10:00",
"closes": "14:00",
"dayOfWeek": "Saturday"
}
]
}
}
This is only a basic implementation but still useful. As of now it does not support break times.
Mocking#
For testing purpose you may need a lot of entities but don’t have them.
Here comes MockerNgsi. In the following example it generates from a given entity 100 new mocked entities.
from ngsildclient import Entity, MockerNgsi
entity = Entity("AirQualityObserved", "Madrid-28079004-2016-03-15T11:00:00Z")
entity.prop("NO2", 22, unitcode="GP")
# generate 100 mocked entities
mocker = MockerNgsi()
mocked_entities = mocker.mock(entity, 100)
Have a look at the first mocked entity in the list.
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z:Mocked:a_m7D8qkQBCIjlTnkY7J~g",
"type": "AirQualityObserved",
"NO2": {
"type": "Property",
"value": 22,
"unitCode": "GP"
},
"mocked": {
"type": "Property",
"value": true
}
}
The id has been suffixed with a unique mock-identifier. A mocked property has been added.
This is the default behaviour of the MockerNgsi class.
It’s up to you to implement your custom mocking logic by providing your own f_mock_id and f_mock_payload functions.
import random
def randomize_NO2(entity: Entity):
entity.prop("mocked", True)
entity["NO2.value"] += random.uniform(-3.0, 3.0)
# generate 100 mocked entities
mocker = MockerNgsi(f_mock_payload=randomize_NO2)
mocked_entities = mocker.mock(entity, 100)
{
"@context": [
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"
],
"id": "urn:ngsi-ld:AirQualityObserved:Madrid-28079004-2016-03-15T11:00:00Z:Mocked:uIzmaZfVT3ulIcR8UY8cXg",
"type": "AirQualityObserved",
"NO2": {
"type": "Property",
"value": 22.830140401969413,
"unitCode": "GP"
},
"mocked": {
"type": "Property",
"value": true
}
}
For further information look at the MockerNgsi class documentation.
- 1(1,2)
Note that all NGSI-LD datetimes are UTC. The library will always convert datetimes to UTC, either naive or aware. The downside of not specifying the timezone is that the result depends on your local environment therefore code execution is not reproducible. Behaviour may change if your code is run in a different place/timezone.
- ETSI_WP42
Guidelines for Modelling with NGSI-LD ETSI WhitePaper