Restricting results of $expand parameter in SensorThings API - iot

I am attempting to truncate the results of an $expand parameter from SensorThingsAPI e.g.
http://example.org/v1.0/Things?$expand=Datastreams
However, $top only restricts the trunk of the query (e.g. Things). Is there a way to truncate the results of the 'leaves' of an $expand?

In this case, the server-side pagination should be controlling the 'leaves' of an $expand.
For example, if the service limit 100 entities for each response and the expanded entities (or the collection) have more than 100, the service will return the top 100 entities following a service-defined order. A #iot.nextLink will also be returned, so that the client know how to fetch the next 100 entities (i.e., next page). Using the above query as an example, an example nextLink to retrieve the Datastreams will be
Datastreams#iot.nextLink:"http://URL_to_retrieve_the_next_page/"
You can use this OGC SensorThings sandbox to see an example return of $expand: http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams?$expand=Observations
The following JSON shows an example response of the following query with $expand: http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams?$expand=Observations:
{
"#iot.count": 1,
"value": [{
"#iot.id": 8,
"#iot.selfLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams(8)",
"description": "Daily Water level",
"observationType": "http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Observation",
"unitOfMeasurement": {
"symbol": "m",
"name": "meter",
"definition": "https://en.wikipedia.org/wiki/Metre"
},
"Observations#iot.nextLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams(8)/Observations?$top=3&$skip=3",
"Observations#iot.count": 1826,
"Observations": [{
"#iot.id": 1835,
"#iot.selfLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Observations(1835)",
"phenomenonTime": "2015-12-30T16:00:00.000Z",
"result": "1375.44",
"resultTime": null,
"Datastream#iot.navigationLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Observations(1835)/Datastream",
"FeatureOfInterest#iot.navigationLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Observations(1835)/FeatureOfInterest"
}],
"ObservedProperty#iot.navigationLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams(8)/ObservedProperty",
"Sensor#iot.navigationLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams(8)/Sensor",
"Thing#iot.navigationLink": "http://scratchpad.sensorup.com/OGCSensorThings/v1.0/Datastreams(8)/Thing"
},{},{}]
}

Related

FastAPI swagger doesn't like list of strings passed via query parameter but endpoint works in browser

I have a problem with a REST API endpoint in FastAPI that accepts a list of strings via a single query parameter. An example of this endpoint's usage is:
http://127.0.0.1:8000/items/2?short=false&response=this&response=that
Here, the parameter named 'response' accepts a list of strings as documented in FastAPI tutorial, section on Query Parameters and String Validation. The endpoint works as expected in the browser.
However, it does not work in Swagger docs. The button labeled 'Add string item' shakes upon clicking 'Execute' to test the endpoint. Swagger UI seems unable to create the expected URL with the embedded query parameters (as shown in Fig 1.).
The code for the endpoint is as follows. I have tried with and without validation.
#app.get("/items/{item_ID}")
async def getQuestion_byID(item_ID: int = Path(
...,
title = "Numeric ID of the question",
description = "Specify a number between 1 and 999",
ge = 1,
le = 999
), response: Optional[List[str]] = Query(
[],
title="Furnish an answer",
description="Answer can only have letters of the alphabet and is case-insensitive",
min_length=3,
max_length=99,
regex="^[a-zA-Z]+$"
), short: bool = Query(
False,
title="Set flag for short result",
description="Acceptable values are 1, True, true, on, yes"
)):
"""
Returns the quiz question or the result.
Accepts item ID as path parameter and
optionally response as query parameter.
Returns result when the response is passed with the item ID.
Otherwise, returns the quiz question.
"""
item = question_bank.get(item_ID, None)
if not item:
return {"question": None}
if response:
return evaluate_response(item_ID, response, short)
else:
return {"question": item["question"]}
Grateful for any help.
As described here, this happens due to that OpenAPI applies the pattern (as well as minimum and maximum constraints) to the schema of the array itself, not just the individual items in the array. If you checked the OpenAPI schema at http://127.0.0.1:8000/openapi.json, you would see that the schema for the response parameter appears as shown below (i.e., validations are being applied to the array itself as well):
{
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"required": false,
"schema": {
"title": "Furnish an answer",
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "array",
"items": {
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "string"
},
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"default": []
},
"name": "response",
"in": "query"
}
Solution 1
As mentioned here, you could use a Pydantic constr instead to specify items with that contraint:
my_constr = constr(regex="^[a-zA-Z]+$", min_length=3, max_length=99)
response: Optional[List[my_constr]] = Query([], title="Furnish an...", description="Answer can...")
Solution 2
Keep your response parameter as is. Copy the OpenAPI schema from http://127.0.0.1:8000/openapi.json, remove the pattern (as well as minimum and maximum attributes) from response's (array) schema and save the OpenAPI schema to a new file (e.g., my_openapi.json). It should look like this:
...
{
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"required": false,
"schema": {
"title": "Furnish an answer",
"type": "array",
"items": {
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "string"
},
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"default": []
},
"name": "response",
"in": "query"
},
...
Then, in your app, instruct FastAPI to use that schema instead:
import json
app.openapi_schema = json.load(open("my_openapi.json"))
Solution 3
Since the above solution would require you to copy and edit the schema every time you make a change or add new endpoints/parameters, you would rather modify the OpenAPI schema as described here. This would save you from copying/editing the schema file. Make sure to add the below at the end of your code (after defining all the routes).
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="FastAPI",
version="0.1.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["maxLength"]
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["minLength"]
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["pattern"]
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
In all the above solutions, the constraints annotation that would normally be shown in OpenAPI under response (i.e., (query) maxLength: 99 minLength: 3 pattern: ^[a-zA-Z]+$), won't appear (since Swagger would create that annotation from the constraints applied to the array, not the items), but there doesn't seem to be a way to preserve that. In Solutions 2 and 3, however, you could modify the "in" attribute, shown in the JSON code snippet above, to manually ddd the annotation. But, as HTML elements, etc., are controlled by Swagger, the whole annotation would appear inside parentheses and without line breaks between the constraints. Nevertheless, you could still inform users about the constraints applied to items, by specifying them in the description of your Query parameter.

createdBy and lastModifiedBy fields missing in drive search results in Microsoft Graph API

I'm sending a search request to the /me/drive/root/search endpoint. I'm specifying which fields to return using the $select query parameter as follows:
https://graph.microsoft.com/v1.0/me/drive/root/search(q='sdflkdshflkjhsdf')?$select=webUrl,name,lastModifiedDateTime,createdBy,lastModifiedBy,createdDateTime,folder,image,id
However the drive item objects returned are missing some of the fields I requested. For example here is the response to the above query:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(driveItem)",
"#odata.nextLink": "https://graph.microsoft.com/v1.0/me/drive/root/search(q='sdflkdshflkjhsdf')?$select=webUrl%2cname%2clastModifiedDateTime%2ccreatedBy%2clastModifiedBy%2ccreatedDateTime%2cfolder%2cimage%2cid&$skiptoken=s!MTtlMDIzYmJlYi03NmUwLTRmMGUtYTljMy02MmE0MTNiM2RlZDE",
"value": [
{
"#odata.type": "#microsoft.graph.driveItem",
"createdDateTime": "2017-08-03T12:59:28Z",
"id": "01ZGVHW2FL7NNJJOD5AVGIMHTNV2BTATLB",
"lastModifiedDateTime": "2017-08-03T12:59:28Z",
"name": "sdflkdshflkjhsdf.pdf",
"webUrl": "https://[REDACTED].sharepoint.com/personal/oscar_[REDACTED]/Documents/sdflkdshflkjhsdf.pdf"
}
]
}
As you can see the createdBy and lastModifiedBy fields have not been returned. Are these fields not supported on this endpoint? The documentation says the endpoint returns full DriveItem objects which should include these fields.
I had same issue. In documentation is said, that
Drive Recent
Drive Item search that both should return the same object DriveItem.
When I tried to search
https://graph.microsoft.com/v1.0/me/drive/root/search(q='MY_RECENT_DOC.docx')?$top=1
gave me response without createdBy or lastModifiedBy.
But when i used 'recent API', the same object with the all information was presented.
https://graph.microsoft.com/v1.0/me/drive/recent?$top=1
Like #oscar-robinson said you can use id (what is always visible) and this id you can use to another search where all information will be presented.
https://graph.microsoft.com/v1.0/me/drive/items/{id}

Query in firebase Database in iOS

I work with firebase database, I have the following data,
I need to get all groups names (GName) of a user by his phoneNum, i.e. all groups of specific user, How can I get that in swift 4?
You should consider restructuring your data. If a user belongs to more than one group in your application then you'll probably have to duplicate your user node for every group the user belongs to in your data structure. You can create another JSON object that holds all of the groups that a user belongs to. Here is a sample JSON for you:
{
"users": [{
"xyz123": {
"userId": "xyz123",
"username": "user1",
"phoneNum": "123456",
"groups": [{
"groupId": 1,
"groupName": "aaa"
}, {
"groupId": 2,
"groupName": "bbb"
}]
}
}]
}
As for filtering with the phone number, you can get all users inside a list and filter the result with the phone number criteria
result = result.filter({item.phoneNum == "123456"})
or get phone number of the user to a upper level, call .child() method with the phone number criteria and fetch the specific user.
Also take a look at structuring data part at firebase documentation.
https://firebase.google.com/docs/database/ios/structure-data
Hope that helps.

Microsoft Graph present #nextlink even with $top=1?

We are querying data using MS Graph OData API, but I found msGraph presents nextlink unexpectedly.
For example, even though I intend to query only one record by specifying $top=1:
https://graph.microsoft.com/v1.0/users?$top=1
in the response there is a #nextlink, which leads to another query to this link by our tool.
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
"**#odata.nextLink**": "https://graph.microsoft.com/v1.0/users?$top=1&$skiptoken=X%2744537074020001000000223A4164616D73404D333635783231343335352E6F6E6D6963726F736F66742E636F6D29557365725F36653762373638652D303765322D343831302D383435392D343835663834663866323034B900000000000000000000%27",
"value": [
{
"id": "6e7b768e-07e2-4810-8459-485f84f8f204",
"businessPhones": [],
"displayName": "Conf Room Adams",
"givenName": null,
"jobTitle": null,
"mail": "Adams#M365x214355.onmicrosoft.com",
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null,
"surname": null,
"userPrincipalName": "Adams#M365x214355.onmicrosoft.com"
}
]
}
One can reproduce it in graph-explorer
Is it working as expected?
I think there should be no nextlink since I only want one record. Is this a bug?
Using $top=x doesn't actually mean
I want you to show me only first x record(s) and ignore the rest
Its role (based on the query parameter docs) is:
Sets the page size of results.
What you saw is the expected outcome, as using $top is one of the cases where paging is used:
Some queries against Microsoft Graph return multiple pages of data either due to server-side paging or due to the use of the $top query parameter to specifically limit the page size in a request.
And further:
When a result set spans multiple pages, Microsoft Graph returns an #odata.nextLink property in the response that contains a URL to the next page of results.
Which precisely answers your question. If you want. feel free to read more about paging here.
Also remember that while using $top query parameter it's a good practice to remember about sorting (source: OData official documentation).

oData - how to use filter for specific odata.type

My oData query is as follow.
http://localhost:21005/api/v1/Devices?$expand=Jobs
My oData JSON response (in Postman's Pretty format) is as follow.
{
"#odata.context": "http://localhost:21005/api/v1/$metadata#Devices",
_"value": [
{
"Id": "abc03c74-8697-49ec-85e6-6444112d0336",
"TimeOffset": 0,
"TimeOffsetMode": "Unmanaged",
"Jobs": [
{
"#odata.type": "#VT.Api.Models.GetDataJob",
"Id": "ba07d50a-f17d-4c65-b3cf-f3e03d1ba1cf"
},
{
"#odata.type": "#VT.Api.Models.GetDataProfilerJob",
"Id": "5aa9c046-e4f2-44de-b932-16c06b86b084"
},
{
"#odata.type": "#VT.Api.Models.GetDeviceConfigurationJob",
"Id": "d7dc0ac5-1f89-4356-aaa8-9ac40353e1af"
}
}
{
"Id": "d42ac1f0-1261-4100-8391-013a226ff25f",
"TimeOffset": 0,
"TimeOffsetMode": "Unmanaged",
"Jobs": [ ]
}
}
Now, I want to query for only specific "#oData.type"
For example, I just want all the "Jobs" whose data type is "#VT.Api.Models.GetDataJob" ("#odata.type": "#VT.Api.Models.GetDataJob"). What kind of filter or query should I use?
The query should be:
http://localhost:21005/api/v1/Devices?$expand=Jobs($filter=isof('VT.Api.Models.GetDataJob'))
but as Brad said, IsOf has not been implemented in webapi odata v4.
According to the current spec, the correct way would be to apply a type filter to the expand expression, e.g.:
http://host/service/Orders?$expand=Customer/Model.VipCustomer
or, using your example,
http://localhost:21005/api/v1/Devices?$expand=Jobs/VT.Api.Models.GetDataJob
Please note that this will return all devices, expand their Jobs association, and only add GetDataJob instances to the resulting association sets. If you only want to query devices that have GetDataJob instances, you will need to see if you can use lambdas with type filters.
See also this SO question and OData Version 4.0 Part 2: URL Conventions
According to the OData spec you should be able to use the "IsOf" function to accomplish this. However, I don't believe this has been implemented yet in WebApi OData v4 (assuming that's what you're using):
https://github.com/OData/WebApi/issues/185

Resources