OpenDroneMap-WebODM/nodeodm/api_client.py

139 wiersze
5.2 KiB
Python
Czysty Zwykły widok Historia

"""
An interface to NodeODM's API
https://github.com/pierotofy/NodeODM/blob/master/docs/index.adoc
"""
from requests.packages.urllib3.fields import RequestField
from requests_toolbelt.multipart import encoder
2016-10-25 16:19:14 +00:00
import requests
import mimetypes
import json
import os
2018-06-25 17:31:42 +00:00
from urllib.parse import urlunparse, urlencode
from app.testwatch import TestWatch
2016-09-21 20:54:22 +00:00
# Extends class to support multipart form data
# fields with the same name
# https://github.com/requests/toolbelt/issues/225
class MultipartEncoder(encoder.MultipartEncoder):
"""Multiple files with the same name support, i.e. files[]"""
def _iter_fields(self):
_fields = self.fields
if hasattr(self.fields, 'items'):
_fields = list(self.fields.items())
for k, v in _fields:
for field in self._iter_field(k, v):
yield field
@classmethod
def _iter_field(cls, field_name, field):
file_name = None
file_type = None
file_headers = None
if field and isinstance(field, (list, tuple)):
if all([isinstance(f, (list, tuple)) for f in field]):
for f in field:
yield next(cls._iter_field(field_name, f))
else:
raise StopIteration()
if len(field) == 2:
file_name, file_pointer = field
elif len(field) == 3:
file_name, file_pointer, file_type = field
else:
file_name, file_pointer, file_type, file_headers = field
else:
file_pointer = field
field = RequestField(name=field_name,
data=file_pointer,
filename=file_name,
headers=file_headers)
field.make_multipart(content_type=file_type)
yield field
class ApiClient:
2018-06-25 17:31:42 +00:00
def __init__(self, host, port, token = "", timeout=30):
2016-09-21 20:54:22 +00:00
self.host = host
self.port = port
2018-06-25 17:31:42 +00:00
self.token = token
self.timeout = timeout
2016-09-21 20:54:22 +00:00
2018-06-25 17:31:42 +00:00
def url(self, url, query = {}):
2018-12-04 16:38:01 +00:00
netloc = self.host if (self.port == 80 or self.port == 443) else "{}:{}".format(self.host, self.port)
proto = 'https' if self.port == 443 else 'http'
2016-10-26 22:09:59 +00:00
2018-06-25 17:31:42 +00:00
if len(self.token) > 0:
query['token'] = self.token
2018-12-04 16:38:01 +00:00
return urlunparse((proto, netloc, url, '', urlencode(query), ''))
2016-10-25 16:19:14 +00:00
2016-09-21 20:54:22 +00:00
def info(self):
return requests.get(self.url('/info'), timeout=self.timeout).json()
def options(self):
return requests.get(self.url('/options'), timeout=self.timeout).json()
2016-10-25 14:47:49 +00:00
def task_info(self, uuid):
return requests.get(self.url('/task/{}/info').format(uuid), timeout=self.timeout).json()
@TestWatch.watch()
2016-11-01 21:12:13 +00:00
def task_output(self, uuid, line = 0):
2018-06-25 17:31:42 +00:00
return requests.get(self.url('/task/{}/output', {'line': line}).format(uuid), timeout=self.timeout).json()
2016-11-01 21:12:13 +00:00
def task_cancel(self, uuid):
return requests.post(self.url('/task/cancel'), data={'uuid': uuid}, timeout=self.timeout).json()
def task_remove(self, uuid):
return requests.post(self.url('/task/remove'), data={'uuid': uuid}, timeout=self.timeout).json()
def task_restart(self, uuid, options = None):
data = {'uuid': uuid}
if options is not None: data['options'] = json.dumps(options)
return requests.post(self.url('/task/restart'), data=data, timeout=self.timeout).json()
2016-11-01 21:12:13 +00:00
def task_download(self, uuid, asset):
2019-01-15 18:40:07 +00:00
res = requests.get(self.url('/task/{}/download/{}').format(uuid, asset), stream=True, timeout=self.timeout)
if "Content-Type" in res.headers and "application/json" in res.headers['Content-Type']:
return res.json()
else:
2016-11-07 17:10:01 +00:00
return res
def new_task(self, images, name=None, options=[], progress_callback=None):
"""
Starts processing of a new task
:param images: list of path images
:param name: name of the task
:param options: options to be used for processing ([{'name': optionName, 'value': optionValue}, ...])
:param progress_callback: optional callback invoked during the upload images process to be used to report status.
:return: UUID or error
"""
# Equivalent as passing the open file descriptor, since requests
# eventually calls read(), but this way we make sure to close
# the file prior to reading the next, so we don't run into open file OS limits
def read_file(path):
with open(path, 'rb') as f:
return f.read()
fields = {
'name': name,
'options': json.dumps(options),
'images': [(os.path.basename(image), read_file(image), (mimetypes.guess_type(image)[0] or "image/jpg")) for image in images]
}
def create_callback(mpe):
total_bytes = mpe.len
def callback(monitor):
if progress_callback is not None and total_bytes > 0:
progress_callback(monitor.bytes_read / total_bytes)
return callback
e = MultipartEncoder(fields=fields)
m = encoder.MultipartEncoderMonitor(e, create_callback(e))
return requests.post(self.url("/task/new"),
data=m,
headers={'Content-Type': m.content_type}).json()