Build Entities
This chapter explains how to build NGSI-LD compliant entities.
Annex 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