Make octoprint_client support multiple instances
Adjusted cli implementation accordingly
This commit is contained in:
parent
5c0a564592
commit
d72c7c144e
2 changed files with 171 additions and 174 deletions
|
|
@ -29,6 +29,7 @@ def client_commands():
|
|||
|
||||
|
||||
@client_commands.group("client", context_settings=dict(ignore_unknown_options=True))
|
||||
@click.option("--apikey", "-a", type=click.STRING)
|
||||
@click.option("--host", "-h", type=click.STRING)
|
||||
@click.option("--port", "-p", type=click.INT)
|
||||
@click.option("--httpuser", type=click.STRING)
|
||||
|
|
@ -36,11 +37,29 @@ def client_commands():
|
|||
@click.option("--https", is_flag=True)
|
||||
@click.option("--prefix", type=click.STRING)
|
||||
@click.pass_context
|
||||
def client(ctx, host, port, httpuser, httppass, https, prefix):
|
||||
def client(ctx, apikey, host, port, httpuser, httppass, https, prefix):
|
||||
"""Basic API client."""
|
||||
try:
|
||||
ctx.obj.settings = init_settings(get_ctx_obj_option(ctx, "basedir", None), get_ctx_obj_option(ctx, "configfile", None))
|
||||
octoprint_client.init_client(ctx.obj.settings, https=https, httpuser=httpuser, httppass=httppass, host=host, port=port, prefix=prefix)
|
||||
if not host or not port or not apikey:
|
||||
settings = init_settings(get_ctx_obj_option(ctx, "basedir", None), get_ctx_obj_option(ctx, "configfile", None))
|
||||
|
||||
if not host:
|
||||
host = settings.get(["server", "host"])
|
||||
host = host if host != "0.0.0.0" else "127.0.0.1"
|
||||
if not port:
|
||||
port = settings.getInt(["server", "port"])
|
||||
|
||||
if not apikey:
|
||||
apikey = settings.get(["api", "key"])
|
||||
|
||||
baseurl = octoprint_client.build_base_url(https=https,
|
||||
httpuser=httpuser,
|
||||
httppass=httppass,
|
||||
host=host,
|
||||
port=port,
|
||||
prefix=prefix)
|
||||
|
||||
ctx.obj.client = octoprint_client.Client(baseurl, apikey)
|
||||
except FatalStartupError as e:
|
||||
click.echo(e.message, err=True)
|
||||
click.echo("There was a fatal error initializing the client.", err=True)
|
||||
|
|
@ -60,27 +79,30 @@ def log_response(response, status_code=True, body=True, headers=False):
|
|||
|
||||
@client.command("get")
|
||||
@click.argument("path")
|
||||
def get(path):
|
||||
@click.pass_context
|
||||
def get(ctx, path):
|
||||
"""Performs a GET request against the specified server path."""
|
||||
r = octoprint_client.get(path)
|
||||
r = ctx.obj.client.get(path)
|
||||
log_response(r)
|
||||
|
||||
|
||||
@client.command("post_json")
|
||||
@click.argument("path")
|
||||
@click.argument("data", type=JsonStringParamType())
|
||||
def post_json(path, data):
|
||||
@click.pass_context
|
||||
def post_json(ctx, path, data):
|
||||
"""POSTs JSON data to the specified server path."""
|
||||
r = octoprint_client.post_json(path, data)
|
||||
r = ctx.obj.client.post_json(path, data)
|
||||
log_response(r)
|
||||
|
||||
|
||||
@client.command("patch_json")
|
||||
@click.argument("path")
|
||||
@click.argument("data", type=JsonStringParamType())
|
||||
def patch_json(path, data):
|
||||
@click.pass_context
|
||||
def patch_json(ctx, path, data):
|
||||
"""PATCHes JSON data to the specified server path."""
|
||||
r = octoprint_client.patch(path, data, encoding="json")
|
||||
r = ctx.obj.client.patch(path, data, encoding="json")
|
||||
log_response(r)
|
||||
|
||||
|
||||
|
|
@ -89,7 +111,8 @@ def patch_json(path, data):
|
|||
@click.argument("file_path", type=click.Path(exists=True, dir_okay=False, resolve_path=True))
|
||||
@click.option("--json", is_flag=True)
|
||||
@click.option("--yaml", is_flag=True)
|
||||
def post_from_file(path, file_path, json_flag, yaml_flag):
|
||||
@click.pass_context
|
||||
def post_from_file(ctx, path, file_path, json_flag, yaml_flag):
|
||||
"""POSTs JSON data to the specified server path."""
|
||||
if json_flag or yaml_flag:
|
||||
if json_flag:
|
||||
|
|
@ -100,12 +123,12 @@ def post_from_file(path, file_path, json_flag, yaml_flag):
|
|||
with open(file_path, "rb") as fp:
|
||||
data = yaml.safe_load(fp)
|
||||
|
||||
r = octoprint_client.post_json(path, data)
|
||||
r = ctx.obj.client.post_json(path, data)
|
||||
else:
|
||||
with open(file_path, "rb") as fp:
|
||||
data = fp.read()
|
||||
|
||||
r = octoprint_client.post(path, data)
|
||||
r = ctx.obj.client.post(path, data)
|
||||
|
||||
log_response(r)
|
||||
|
||||
|
|
@ -117,13 +140,14 @@ def post_from_file(path, file_path, json_flag, yaml_flag):
|
|||
@click.option("--int", "-i", "int_params", multiple=True, nargs=2, type=click.Tuple([unicode, int]))
|
||||
@click.option("--float", "-f", "float_params", multiple=True, nargs=2, type=click.Tuple([unicode, float]))
|
||||
@click.option("--bool", "-b", "bool_params", multiple=True, nargs=2, type=click.Tuple([unicode, bool]))
|
||||
def command(path, command, str_params, int_params, float_params, bool_params):
|
||||
@click.pass_context
|
||||
def command(ctx, path, command, str_params, int_params, float_params, bool_params):
|
||||
"""Sends a JSON command to the specified server path."""
|
||||
data = dict()
|
||||
params = str_params + int_params + float_params + bool_params
|
||||
for param in params:
|
||||
data[param[0]] = param[1]
|
||||
r = octoprint_client.post_command(path, command, additional=data)
|
||||
r = ctx.obj.client.post_command(path, command, additional=data)
|
||||
log_response(r, body=False)
|
||||
|
||||
|
||||
|
|
@ -133,26 +157,29 @@ def command(path, command, str_params, int_params, float_params, bool_params):
|
|||
@click.option("--parameter", "-P", "params", multiple=True, nargs=2, type=click.Tuple([unicode, unicode]))
|
||||
@click.option("--file-name", type=click.STRING)
|
||||
@click.option("--content-type", type=click.STRING)
|
||||
def upload(path, file_path, params, file_name, content_type):
|
||||
@click.pass_context
|
||||
def upload(ctx, path, file_path, params, file_name, content_type):
|
||||
"""Uploads the specified file to the specified server path."""
|
||||
data = dict()
|
||||
for param in params:
|
||||
data[param[0]] = param[1]
|
||||
|
||||
r = octoprint_client.upload(path, file_path, additional=data, file_name=file_name, content_type=content_type)
|
||||
r = ctx.obj.client.upload(path, file_path, additional=data, file_name=file_name, content_type=content_type)
|
||||
log_response(r)
|
||||
|
||||
|
||||
@client.command("delete")
|
||||
@click.argument("path")
|
||||
def delete(path):
|
||||
@click.pass_context
|
||||
def delete(ctx, path):
|
||||
"""Sends a DELETE request to the specified server path."""
|
||||
r = octoprint_client.delete(path)
|
||||
r = ctx.obj.client.delete(path)
|
||||
log_response(r)
|
||||
|
||||
|
||||
@client.command("listen")
|
||||
def listen():
|
||||
@click.pass_context
|
||||
def listen(ctx):
|
||||
def on_connect(ws):
|
||||
click.echo(">>> Connected!")
|
||||
|
||||
|
|
@ -168,11 +195,11 @@ def listen():
|
|||
def on_message(ws, message_type, message_payload):
|
||||
click.echo("Message: {}, Payload: {}".format(message_type, json.dumps(message_payload)))
|
||||
|
||||
socket = octoprint_client.connect_socket(on_connect=on_connect,
|
||||
on_close=on_close,
|
||||
on_error=on_error,
|
||||
on_heartbeat=on_heartbeat,
|
||||
on_message=on_message)
|
||||
socket = ctx.obj.client.create_socket(on_connect=on_connect,
|
||||
on_close=on_close,
|
||||
on_error=on_error,
|
||||
on_heartbeat=on_heartbeat,
|
||||
on_message=on_message)
|
||||
|
||||
click.echo(">>> Waiting for client to exit")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -8,8 +8,15 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms
|
|||
import requests
|
||||
import time
|
||||
|
||||
apikey = None
|
||||
baseurl = None
|
||||
|
||||
def build_base_url(https=False, httpuser=None, httppass=None, host=None, port=None, prefix=None):
|
||||
protocol = "https" if https else "http"
|
||||
httpauth = "{}:{}@".format(httpuser, httppass) if httpuser and httppass else ""
|
||||
host = host if host else "127.0.0.1"
|
||||
port = ":{}".format(port) if port else ":5000"
|
||||
prefix = prefix if prefix else ""
|
||||
|
||||
return "{}://{}{}{}{}".format(protocol, httpauth, host, port, prefix)
|
||||
|
||||
|
||||
class SocketTimeout(BaseException):
|
||||
|
|
@ -202,183 +209,146 @@ class SocketClient(object):
|
|||
# a reconnect, that's a failure
|
||||
return False
|
||||
|
||||
class Client(object):
|
||||
|
||||
def build_base_url(https=False, httpuser=None, httppass=None, host=None, port=None, prefix=None):
|
||||
protocol = "https" if https else "http"
|
||||
httpauth = "{}:{}@".format(httpuser, httppass) if httpuser and httppass else ""
|
||||
host = host if host else "127.0.0.1"
|
||||
port = ":{}".format(port) if port else ":5000"
|
||||
prefix = prefix if prefix else ""
|
||||
def __init__(self, baseurl, apikey):
|
||||
self.baseurl = baseurl
|
||||
self.apikey = apikey
|
||||
|
||||
return "{}://{}{}{}{}".format(protocol, httpauth, host, port, prefix)
|
||||
def prepare_request(self, method=None, path=None, params=None):
|
||||
url = None
|
||||
if self.baseurl:
|
||||
while path.startswith("/"):
|
||||
path = path[1:]
|
||||
url = self.baseurl + "/" + path
|
||||
return requests.Request(method=method, url=url, params=params, headers={"X-Api-Key": self.apikey}).prepare()
|
||||
|
||||
def request(self, method, path, data=None, files=None, encoding=None, params=None):
|
||||
s = requests.Session()
|
||||
request = self.prepare_request(method, path, params=params)
|
||||
if data or files:
|
||||
if encoding == "json":
|
||||
request.prepare_body(None, None, json=data)
|
||||
else:
|
||||
request.prepare_body(data, files=files)
|
||||
response = s.send(request)
|
||||
return response
|
||||
|
||||
def init_client(settings, https=False, httpuser=None, httppass=None, host=None, port=None, prefix=None):
|
||||
"""
|
||||
Initializes the API client with the provided settings.
|
||||
def get(self, path, params=None):
|
||||
return self.request("GET", path, params=params)
|
||||
|
||||
Basically a convenience method to set ``apikey`` and ``baseurl`` from settings
|
||||
and/or command line arguments.
|
||||
def post(self, path, data, encoding=None, params=None):
|
||||
return self.request("POST", path, data=data, encoding=encoding, params=params)
|
||||
|
||||
Arguments:
|
||||
settings (octoprint.settings.Settings): A :class:`~octoprint.settings.Settings` instance to use
|
||||
for client configuration
|
||||
https (bool): Whether to connect via HTTPS (True) or not (False, default)
|
||||
httpuser (str or None): HTTP Basic Auth username to use. No Basic Auth will be
|
||||
used if unset.
|
||||
httppass (str or None): HTTP Basic Auth password to use. No Basic Auth will be
|
||||
used if unset.
|
||||
host (str or None): Host to connect to, overrides data from settings if set.
|
||||
port (int or None): Port to connect to, overrides data from settings if set.
|
||||
prefix (str or None): Path prefix, overrides data from settings if set.
|
||||
"""
|
||||
settings_host = settings.get(["server", "host"])
|
||||
settings_port = settings.getInt(["server", "port"])
|
||||
settings_apikey = settings.get(["api", "key"])
|
||||
def post_json(self, path, data, params=None):
|
||||
return self.post(path, data, encoding="json", params=params)
|
||||
|
||||
global apikey, baseurl
|
||||
apikey = settings_apikey
|
||||
baseurl = build_base_url(https=https,
|
||||
httpuser=httpuser,
|
||||
httppass=httppass,
|
||||
host=host or settings_host if settings_host != "0.0.0.0" else "127.0.0.1",
|
||||
port=port or settings_port,
|
||||
prefix=prefix)
|
||||
def post_command(self, path, command, additional=None):
|
||||
data = dict(command=command)
|
||||
if additional:
|
||||
data.update(additional)
|
||||
return self.post_json(path, data, params=data)
|
||||
|
||||
def prepare_request(method=None, path=None, params=None):
|
||||
url = None
|
||||
if baseurl:
|
||||
while path.startswith("/"):
|
||||
path = path[1:]
|
||||
url = baseurl + "/" + path
|
||||
return requests.Request(method=method, url=url, params=params, headers={"X-Api-Key": apikey}).prepare()
|
||||
def upload(self, path, file_path, additional=None, file_name=None, content_type=None, params=None):
|
||||
import os
|
||||
|
||||
def request(method, path, data=None, files=None, encoding=None, params=None):
|
||||
s = requests.Session()
|
||||
request = prepare_request(method, path, params=params)
|
||||
if data or files:
|
||||
if encoding == "json":
|
||||
request.prepare_body(None, None, json=data)
|
||||
else:
|
||||
request.prepare_body(data, files=files)
|
||||
response = s.send(request)
|
||||
return response
|
||||
if not os.path.isfile(file_path):
|
||||
raise ValueError("{} cannot be uploaded since it is not a file".format(file_path))
|
||||
|
||||
def get(path, params=None):
|
||||
return request("GET", path, params=params)
|
||||
if file_name is None:
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
def post(path, data, encoding=None, params=None):
|
||||
return request("POST", path, data=data, encoding=encoding, params=params)
|
||||
with open(file_path, "rb") as fp:
|
||||
if content_type:
|
||||
files = dict(file=(file_name, fp, content_type))
|
||||
else:
|
||||
files = dict(file=(file_name, fp))
|
||||
|
||||
def post_json(path, data, params=None):
|
||||
return post(path, data, encoding="json", params=params)
|
||||
response = self.request("POST", path, data=additional, files=files, params=params)
|
||||
|
||||
def post_command(path, command, additional=None):
|
||||
data = dict(command=command)
|
||||
if additional:
|
||||
data.update(additional)
|
||||
return post_json(path, data, params=data)
|
||||
return response
|
||||
|
||||
def upload(path, file_path, additional=None, file_name=None, content_type=None, params=None):
|
||||
import os
|
||||
def delete(self, path, params=None):
|
||||
return self.request("DELETE", path, params=params)
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
raise ValueError("{} cannot be uploaded since it is not a file".format(file_path))
|
||||
def patch(self, path, data, encoding=None, params=None):
|
||||
return self.request("PATCH", path, data=data, encoding=encoding, params=params)
|
||||
|
||||
if file_name is None:
|
||||
file_name = os.path.basename(file_path)
|
||||
def put(self, path, data, encoding=None, params=None):
|
||||
return self.request("PUT", path, data=data, encoding=encoding, params=params)
|
||||
|
||||
with open(file_path, "rb") as fp:
|
||||
if content_type:
|
||||
files = dict(file=(file_name, fp, content_type))
|
||||
else:
|
||||
files = dict(file=(file_name, fp))
|
||||
def create_socket(self, **kwargs):
|
||||
import uuid
|
||||
import random
|
||||
import json
|
||||
|
||||
response = request("POST", path, data=additional, files=files, params=params)
|
||||
# creates websocket URL for SockJS according to
|
||||
# - http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-37
|
||||
# - http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-50
|
||||
url = "ws://{}/sockjs/{:0>3d}/{}/websocket".format(
|
||||
self.baseurl[self.baseurl.find("//") + 2:], # host + port + prefix, but no protocol
|
||||
random.randrange(0, stop=999), # server_id
|
||||
uuid.uuid4() # session_id
|
||||
)
|
||||
use_ssl = self.baseurl.startswith("https:")
|
||||
|
||||
return response
|
||||
on_open_cb = kwargs.get("on_open", None)
|
||||
on_heartbeat_cb = kwargs.get("on_heartbeat", None)
|
||||
on_message_cb = kwargs.get("on_message", None)
|
||||
on_close_cb = kwargs.get("on_close", None)
|
||||
on_error_cb = kwargs.get("on_error", None)
|
||||
daemon = kwargs.get("daemon", True)
|
||||
|
||||
def delete(path, params=None):
|
||||
return request("DELETE", path, params=params)
|
||||
def on_message(ws, message):
|
||||
message_type = message[0]
|
||||
|
||||
def patch(path, data, encoding=None, params=None):
|
||||
return request("PATCH", path, data=data, encoding=encoding, params=params)
|
||||
|
||||
def put(path, data, encoding=None, params=None):
|
||||
return request("PUT", path, data=data, encoding=encoding, params=params)
|
||||
|
||||
def connect_socket(**kwargs):
|
||||
import uuid
|
||||
import random
|
||||
import json
|
||||
|
||||
# creates websocket URL for SockJS according to
|
||||
# - http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-37
|
||||
# - http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-50
|
||||
url = "ws://{}/sockjs/{:0>3d}/{}/websocket".format(
|
||||
baseurl[baseurl.find("//") + 2:], # host + port + prefix, but no protocol
|
||||
random.randrange(0, stop=999), # server_id
|
||||
uuid.uuid4() # session_id
|
||||
)
|
||||
use_ssl = baseurl.startswith("https:")
|
||||
|
||||
on_open_cb = kwargs.get("on_open", None)
|
||||
on_heartbeat_cb = kwargs.get("on_heartbeat", None)
|
||||
on_message_cb = kwargs.get("on_message", None)
|
||||
on_close_cb = kwargs.get("on_close", None)
|
||||
on_error_cb = kwargs.get("on_error", None)
|
||||
daemon = kwargs.get("daemon", True)
|
||||
|
||||
def on_message(ws, message):
|
||||
message_type = message[0]
|
||||
|
||||
if message_type == "h":
|
||||
# "heartbeat" message
|
||||
if callable(on_heartbeat_cb):
|
||||
on_heartbeat_cb(ws)
|
||||
if message_type == "h":
|
||||
# "heartbeat" message
|
||||
if callable(on_heartbeat_cb):
|
||||
on_heartbeat_cb(ws)
|
||||
return
|
||||
elif message_type == "o":
|
||||
# "open" message
|
||||
return
|
||||
elif message_type == "c":
|
||||
# "close" message
|
||||
return
|
||||
elif message_type == "o":
|
||||
# "open" message
|
||||
return
|
||||
elif message_type == "c":
|
||||
# "close" message
|
||||
return
|
||||
|
||||
if not callable(on_message_cb):
|
||||
return
|
||||
if not callable(on_message_cb):
|
||||
return
|
||||
|
||||
message_body = message[1:]
|
||||
if not message_body:
|
||||
return
|
||||
message_body = message[1:]
|
||||
if not message_body:
|
||||
return
|
||||
|
||||
data = json.loads(message_body)
|
||||
data = json.loads(message_body)
|
||||
|
||||
if message_type == "m":
|
||||
data = [data,]
|
||||
if message_type == "m":
|
||||
data = [data,]
|
||||
|
||||
for d in data:
|
||||
for internal_type, internal_message in d.items():
|
||||
on_message_cb(ws, internal_type, internal_message)
|
||||
for d in data:
|
||||
for internal_type, internal_message in d.items():
|
||||
on_message_cb(ws, internal_type, internal_message)
|
||||
|
||||
def on_open(ws):
|
||||
if callable(on_open_cb):
|
||||
on_open_cb(ws)
|
||||
def on_open(ws):
|
||||
if callable(on_open_cb):
|
||||
on_open_cb(ws)
|
||||
|
||||
def on_close(ws):
|
||||
if callable(on_close_cb):
|
||||
on_close_cb(ws)
|
||||
def on_close(ws):
|
||||
if callable(on_close_cb):
|
||||
on_close_cb(ws)
|
||||
|
||||
def on_error(ws, error):
|
||||
if callable(on_error_cb):
|
||||
on_error_cb(ws, error)
|
||||
def on_error(ws, error):
|
||||
if callable(on_error_cb):
|
||||
on_error_cb(ws, error)
|
||||
|
||||
socket = SocketClient(url,
|
||||
use_ssl=use_ssl,
|
||||
daemon=daemon,
|
||||
on_open=on_open,
|
||||
on_message=on_message,
|
||||
on_close=on_close,
|
||||
on_error=on_error)
|
||||
socket.connect()
|
||||
socket = SocketClient(url,
|
||||
use_ssl=use_ssl,
|
||||
daemon=daemon,
|
||||
on_open=on_open,
|
||||
on_message=on_message,
|
||||
on_close=on_close,
|
||||
on_error=on_error)
|
||||
socket.connect()
|
||||
|
||||
return socket
|
||||
return socket
|
||||
|
|
|
|||
Loading…
Reference in a new issue