Web API¶
This section provides the documentation to the web API implemented in InstrumentDB. You can get a view of the RESTful API calls using either the Swagger and Redoc pages, available at the addresses http://server/swagger/ and http://server/redoc/.
Format specifications¶
You can query the list of format specifications by issuing a GET
command to the url http://server/api/format_specs/. To get information
about one entity, append the full UUID and a slash to it:
http://server/api/format_specs/ede55e77-0dd1-483e-a307-e6e67759a450/.
To create a new entity, issue a POST
command and pass a JSON
dictionary containing the following fields:
document_ref
: a string uniquely identifying the document. The format of the string depends on the conventions used by the collaboration developing the instrument.title
: a string containing the title of the specification document.doc_file
: a file-like object containing the format specification documentdoc_file_name
: the file name to be suggested to the user when they want to download itdoc_mime_type
: a string containing the MIME type of the specification documentfile_mime_type
: a string containing the MIME type of the file described by this specification document (optional).
Note the difference between doc_mime_type
and file_mime_type
: the
former pertains to the document (DOCX, PDF, LaTeX, …), while the latter
refers to the file described by the specification document. Both are used
as hints to your browser: when you download the specification document
or the data file, letting your webbrowser know the MIME type can
enable more features, e.g., a preview within the “File save” dialog.
Here is an example, where we upload a Microsoft Word DOCX document describing how to interpret an Excel file with the details of some electronic board:
import requests as req
server = "http://127.0.0.1:8000/"
response = req.post(url=f"{server_url}/api/login",
data={"username":"user1", "password": "passwd54321"}
)
assert response.ok
auth_header = {"Authorization": "Token " + response.json()["token"]}
response = req.post(
server + "api/format_specs/",
data={
"document_ref": "DOC0001",
"title": "Specification of the electronics",
"doc_file_name": "DOC0001.docx",
# The specification document is a MS Word file…
"doc_mime_type": "application/vnd.openxmlformats-officedocument",
# …which explains how to interpret the data file, created using MS Excel
"file_mime_type": "application/vnd.openxmlformats-officedocument",
},
files={
"doc_file": open("my_specification.docx", "rb"),
}
headers=auth_header,
)
assert response.ok
print(response.json())
Entities¶
You can query the list of entities by issuing a GET
command to the
url http://server/api/entities/. To get information about one entity,
append the full UUID and a slash to it:
http://server/api/entities/ede55e77-0dd1-483e-a307-e6e67759a450/.
To create a new entity, issue a POST
command and pass a JSON
dictionary containing the following fields:
name
: a stringparent
: the full URL to the parent entity; leave it out if the entity has no parent.children
: a list of full URLs for the child entities; you can leave it out.quantities
: a list of full URLs for each quantity belonging to this entity. Passing[]
is ok.
Here is an example in Python, it creates a new entity and then deletes it immediately after:
import requests as req
server = "http://127.0.0.1:8000/"
response = req.post(url=f"{server_url}/api/login",
data={"username":"user1", "password": "passwd54321"}
)
assert response.ok
auth_header = {"Authorization": "Token " + response.json()["token"]}
response = req.post(
server + "api/entities/",
data={
"name": "my entity",
},
headers=auth_header,
)
assert response.ok
print(response.json())
# Output:
# {'uuid': 'f89e8597-7561-4170-bec3-9837b3f32d61',
# 'url': 'http://127.0.0.1:8000/f89e8597-7561-4170-bec3-9837b3f32d61/',
# 'name': 'my entity',
# 'parent': None,
# 'children': [],
# 'quantities': []}
# Now delete the entity that was created above
req.delete(response.json()["url"], headers=auth_header)
To alter an object, you can use the PATCH
command. The following
example creates an object and then modifies its name:
import requests as req
server = "http://127.0.0.1:8000/"
response = req.post(server + "api/entities/", data={
"name": "my entity",
})
assert response.ok
url = response.json()["url"]
# This command changes "my entity" into "a better name"
req.patch(url, data={"name": "a better name"})
You can also access an entity deeply nested in the tree using
the url http://server/tree/PATH. The PATH
part is a nested
string of entities separated by /
, like for instance
http://server/tree/instrument/electronic_board/board0
.
(Beware that InstrumentDB follows the HTTP protocol and returns
a HTTP 302 FOUND
signal, so your library of choice might
need a further GET
call to follow the alias. More advanced
libraries do this automatically: this is the case of the requests
library we are using in these examples.)
Quantities¶
You can query the list of quantities by issuing a GET
command to
the url http://server/api/quantities/. To get information about one
entity, append the full UUID and a slash to it:
http://server/api/quantities/ede55e77-0dd1-483e-a307-e6e67759a450/.
To create a new quantity, you must issue a POST
command with a
JSON record containing these keys:
name
: a stringformat_spec
: the URL to a format specification objectparent_entity
: the URL to an entitydata_files
: a list of URLs for each data file. Passing[]
is ok.
You can also access a quantity deeply nested in the tree of entities
using a technique similar to the one described above for entities.
If you are looking for a quantity named QUANTITY_NAME
, buried in
a deep branch of the tree of entities, you can use the url
http://server/tree/PATH/QUANTITY_NAME/, where the PATH
part
is a nested string of entities separated by /
.
(Beware that InstrumentDB follows the HTTP protocol and returns
a HTTP 302 FOUND
signal, so your library of choice might
need a further GET
call to follow the alias. More advanced
libraries do this automatically: this is the case of the requests
library we are using in these examples.)
As an example, suppose that the tree of entities is the following:
instrument
|
+-- electronic_board
|
+-- telescope
|
+--- mirror1
|
+--- mirror2
You can retrieve the entity for mirror2
through the URL
http://server/tree/instrument/telescope/mirror2
Data files¶
You can query the list of data files by issuing a GET
command to
the url http://server/api/data_files/. To get information about one
entity, append the full UUID and a slash to it:
http://server/api/data_files/ede55e77-0dd1-483e-a307-e6e67759a450/.
To create a new data file, you must issue a POST
command with a
JSON record containing these keys:
name
: a name to be used when the data file is going to be downloaded locally into an actual file.upload_date
: the date and time when the file was created. If not provided, the current date will be used.file_data
: a file-like object containing the contents of the file.metadata
: a JSON structure containing custom metadata associated with the data file.quantity
: the URL to the quantity that owns this data file.spec_version
: a custom string specifying which version of the specification document (associated withquantity
) was used to produce this data file.dependencies
: a list of URLs to data files that have been used to produce this very data file (optional).plot_mime_type
: the MIME type of the plot associated with this data file (optional).plot_file
: a file-like object containing a visual representation of the data file.comment
: a string containing any comment (optional).release_tags
: a list of URLS to the releases that include this data file (optional).
Creating a POST
command in Python with the
requests library requires you
to send the JSON and (optionally) the two files containing the data
file itself and the plot. You can achieve this using both the data=
and files=
keywords when calling requests.post
, like in the
following example:
import requests as req
server_url = "http://127.0.0.1:8000"
response = req.post(url=f"{server_url}/api/login",
data={"username":"user1", "password": "passwd54321"}
)
assert response.ok
auth_header = {"Authorization": "Token " + response.json()["token"]}
response = req.post(
url=f"{server_url}/api/data_files/",
data={
"name": "My data file",
"quantity": f"{server_url}/api/quantities/4a0c5e12-da9c-4c7a-923e-810a19974444/",
"spec_version": "1.0",
"metadata": "{}",
"plot_mime_type": "image/png", # THIS IS MANDATORY IF YOU INCLUDE "plot_file" BELOW!
},
files={
"file_data": open("/local_storage/spreadsheet.xlsx", "rb"),
"plot_file": open("/local_storage/summary_plot.png", "rb"),
},
headers=auth_header,
)
assert response.ok
uuid = response.json()["uuid"]
print("Data file created, UUID is ", uuid)
It is required that you specify plot_mime_type
if you plan to
pass plot_file
like in the example above, because this will be used
to determine how to show the image when browsing the database through the
web interface.
If a data file is part of a release (see the section Releases below),
you can access it using the url http://server/releases/RELEASE/PATH/QUANTITY,
where RELEASE
is the name of the release, PATH
is the sequence of
of entity names separated by /
, and QUANTITY
is the quantity which
hosts the data file. For instance, if the tree of entities is the following:
instrument
|
+-- electronic_board
|
+-- telescope
|
+--- mirror1
|
+--- mirror2
and the quantity you are looking for is the CAD for mirror2
that
is stored under the quantity design_cad
, you can access the CAD
that was saved in release v2.03
using the path
http://server/releases/v2.03/instrument/telescope/mirror2
Releases¶
You can query the list of releases by issuing a GET
command to the
url http://server/api/releases/. To get information about one release,
append its name and a slash to it: http://server/api/releases/v0.28/.
Finally, to download the JSON file for one release (without attachments!)
append download/
to its URL: http://server/api/releases/v0.28/download/.
To create a new release, you must issue a POST
command with a
JSON record containing these keys:
tag
: the name of the release. The only characters allowed here are letters, digits, the underscore and the dot.rel_date
: the date when the release was created. If not specified, the current date is used.comment
: a string containing any useful comment regarding this release (optional).data_files
: a list of URLs containing the data files.
To associate data files to releases, you can use one of the following approaches:
Add data files to the release tag as soon as you create it;
Add data files to the release tag after having created the release;
Add releases to a data file.
Let’s see each of the three approaches. The first one is the simplest:
import requests as req
server = "http://127.0.0.1:8000/"
# Get authentication token (login)
response = req.post(url=f"{server_url}/api/login",
data={"username":"user1", "password": "passwd54321"}
)
auth_header = {"Authorization": "Token " + response.json()["token"]}
# Name of the release we're going to create
release_name = "v0.10"
# These are the data files to be added to the release
data_files = [
"http://127.0.0.1:8000/api/data_files/021d0dfa-e54a-44ca-abc8-ac1d01ed4c50/",
"http://127.0.0.1:8000/api/data_files/791a310e-f950-4370-bcf0-bc49622847c9/",
"http://127.0.0.1:8000/api/data_files/34c11186-2ce2-4805-9114-91ed460c6a95/",
]
# Create the release
response = requests.post(
server + "api/releases/", data={
"tag": release_name,
"comment": "dummy release",
"data_files": data_files,
},
headers=auth_header,
)
Let’s now consider the case where you did not pass the data_files
key in the POST command above. (For instance, you were still building
the list of data files.) Assuming that a release was already created,
you can use PATCH
commands to modify the release object, as shown
in this snippet:
# We are re-using the "req" object got in the snippet above through
# the call to `requests.post`
release_info = response.json()
# This is the URL of the release we created
url = response.json()["url"]
# We are re-using "tag" and "comment" from the call to `request.post`
# above, but we might change them as well in this call, as the HTTP
# `patch` command overwrites everything.
requests.patch(
url,
data={
"tag": release_info["tag"],
"comment": release_info["comment"],
"data_files": data_files,
},
headers=auth_header,
)
Alternatively, we can go through the opposite route and add the
release tag to every data file in the list data_files
. The
following snippet is equivalent to the code above:
for cur_data_file_url in data_files:
# Retrieve the current data file
cur_data_file = req.get(cur_data_file_url, headers=auth_header).json()
# Append the URL to the new release to the list of release tags
cur_data_file["release_tags"].append(release_info["url"])
# Modify the data file in the database
req.patch(cur_data_file_url, data=cur_data_file, headers=auth_header)