Monday, April 2, 2018¶
I started to dive into OData with the question “How much work will it be to write an OData adapter for Lino?”.
It seems that there is no Python library which provides OData server functionality. Even Steve Lay’s PySLET seems to just forward incoming calls to remote services (*). So basically we need to write something from scratch.
My notes while reading http://www.odata.org/getting-started/basic-tutorial/ (and http://www.odata.org/documentation/).
OData is more than a RESTful interface. It adds metadata (description of the data model), querying (filtering data), custom operations. “The OData Protocol is different from other REST-based web service approaches in that it provides a uniform way to describe both the data and the data model. This improves semantic interoperability between systems and allows an ecosystem to emerge.”
Here is an example of a request for a collection of entities:
GET serviceRoot/People
Response:
{
"@odata.context": "serviceRoot/$metadata#People",
"@odata.nextLink": "serviceRoot/People?%24skiptoken=8",
"value": [
{
"@odata.id": "serviceRoot/People('russellwhyte')",
"@odata.etag": "W/"08D1694BD49A0F11"",
"@odata.editLink": "serviceRoot/People('russellwhyte')",
"UserName": "russellwhyte",
"FirstName": "Russell",
"LastName": "Whyte",
"Emails": [
"Russell@example.com",
"Russell@contoso.com"
],
"AddressInfo": [
{
"Address": "187 Suffolk Ln.",
"City": {
"CountryRegion": "United States",
"Name": "Boise",
"Region": "ID"
}
}
],
"Gender": "Male",
"Concurrency": 635404796846280400
},
...
{
"@odata.id": "serviceRoot/People('keithpinckney')",
"@odata.etag": "W/"08D1694BD49A0F11"",
"@odata.editLink": "serviceRoot/People('keithpinckney')",
"UserName": "keithpinckney",
"FirstName": "Keith",
"LastName": "Pinckney",
"Emails": [
"Keith@example.com",
"Keith@contoso.com"
],
"AddressInfo": [],
"Gender": "Male",
"Concurrency": 635404796846280400
}
]
}
Explanations:
@odata.context
is the base URL to apply to any relative URLs in this response.@odata.nextLink
: link to the next “page” in a collection with partial results. Indicates that the response is only a subset of the requested collection.The
value
is a list of dicts, one dict for each record.
Requesting an Individual Entity by ID¶
Request:
GET serviceRoot/People('russellwhyte')
Response:
{
"@odata.context": "serviceRoot/$metadata#People/$entity",
"@odata.id": "serviceRoot/People('russellwhyte')",
"@odata.etag": "W/"08D1694BF26D2BC9"",
"@odata.editLink": "serviceRoot/People('russellwhyte')",
"UserName": "russellwhyte",
"FirstName": "Russell",
"LastName": "Whyte",
"Emails": [
"Russell@example.com",
"Russell@contoso.com"
],
"AddressInfo": [
{
"Address": "187 Suffolk Ln.",
"City": {
"CountryRegion": "United States",
"Name": "Boise",
"Region": "ID"
}
}
],
"Gender": "Male",
"Concurrency": 635404797346655200
}
Request an individual property:
GET serviceRoot/Airports('KSFO')/Name
Response:
{
"@odata.context": "serviceRoot/$metadata#Airports('KSFO')/Name",
"value": "San Francisco International Airport"
}
Request a raw value:
GET serviceRoot/Airports('KSFO')/Name/$value
Response:
San Francisco International Airport
Property value of a complex type:
{
"@odata.context": "serviceRoot/$metadata#Airports('KSFO')/Location/Address",
"value": "South McDonnell Road, San Francisco, CA 94128"
}
Querying data¶
The $filter
query option:
GET serviceRoot/People?$filter=FirstName eq 'Scott'
Filter on complex type:
GET serviceRoot/Airports?$filter=contains(Location/Address, 'San Francisco')
Filter on Enum Properties:
GET serviceRoot/People?$filter=Gender eq Microsoft.OData.SampleService.Models.TripPin.PersonGender'Female'
(Returns all female People. The Gender is a property of Enum type.)
Nested Filter in $expand
:
GET serviceRoot/People?$expand=Trips($filter=Name eq 'Trip in US')
Return People and all their trips with Name “Trip in US”.
Query option $orderby
:
GET serviceRoot/People('scottketchum')/Trips?$orderby=EndsAt desc
The $top
query option requests the number of items in the queried
collection to be included in the result.
The $skip
query option requests the number of items in the queried
collection that are to be skipped and not included in the result.
Return the first two people of the People entity set:
GET serviceRoot/People?$top=2
Return people starting with the 19th record:
GET serviceRoot/People?$skip=18
The $count
query option requests a count of the matching
resources.
Return the total number of people:
GET serviceRoot/People/$count
Response:
20
The $expand
query option specifies the related resources to be
included in line with retrieved resources.
Return the Friends of a Person:
GET serviceRoot/People('keithpinckney')?$expand=Friends
Response:
{
"@odata.context": "serviceRoot/$metadata#People/$entity",
"@odata.id": "serviceRoot/People('keithpinckney')",
"@odata.etag": "W/"08D1694E2BB4317A"",
"@odata.editLink": "serviceRoot/People('keithpinckney')",
"UserName": "keithpinckney",
"FirstName": "Keith",
"LastName": "Pinckney",
"Emails": [
"Keith@example.com",
"Keith@contoso.com"
],
"AddressInfo": [],
"Gender": "Male",
"Concurrency": 635404806897545600,
"Friends": [
{
"@odata.id": "serviceRoot/People('clydeguess')",
"@odata.etag": "W/"08D1694E2BB4317A"",
"@odata.editLink": "serviceRoot/People('clydeguess')",
"UserName": "clydeguess",
"FirstName": "Clyde",
"LastName": "Guess",
"Emails": [
"Clyde@example.com"
],
"AddressInfo": [],
"Gender": "Male",
"Concurrency": 635404806897545600
},
{ ... }
]
}
The $select
query option requests a limited set of properties for
each entity or complex type.
Return Name and IcaoCode of all Airports:
GET serviceRoot/Airports?$select=Name, IcaoCode
The $search
query option includes only those entities matching the
specified search expression. The definition of what it means to match
is dependent upon the implementation.
Get all People who have ‘Boise’ in their contents:
serviceRoot/People?$search=Boise
(to be continued)