cli config contexts

This commit is contained in:
dave 2021-09-24 22:17:20 -07:00
parent 4268b2df48
commit eb0571c679
1 changed files with 113 additions and 9 deletions

View File

@ -14,15 +14,16 @@ import appdirs
APPNAME = "photocli"
DEFAULT_CONFIG = {"uri": None,
"lastbatch": None}
DEFAULT_CONFIG = {"context": "default",
"contexts": {"default": {"uri": None,
"upload_workers": 2}}}
class CliConfig(dict):
def __init__(self, appname, default):
def __init__(self, appname):
self.confdir = appdirs.user_config_dir(appname)
self.confpath = os.path.join(self.confdir, "cli.json") # ~/Library/Application Support/name/cli.json
self.confpath = os.path.join(self.confdir, "cli.json") # ~/Library/Application Support/photocli/cli.json
def init_config(self):
@ -41,11 +42,41 @@ class CliConfig(dict):
json.dump(self, f, indent=4)
def __setitem__(self, key, value):
prevents setting of invalid config keys as they are not defined
if key not in self:
raise KeyError(key)
super().__setitem__(key, value)
def context(self):
return self["context"]
def set_server(self, context_name, server_uri):
self["contexts"][context_name]["uri"] = server_uri
def select_context(self, context_name):
if context_name not in self["contexts"]:
raise KeyError("Context '{}' is not defined".format(context_name))
return self["contexts"][context_name]
def set_context(self, context_name):
if context_name not in self["contexts"]:
raise KeyError("Context '{}' is not defined".format(context_name))
self["context"] = context_name
def edit_context(self, context_name, context):
self["contexts"][context_name] = context
def delete_context(self, context_name):
del self["context_name"]
class PhotoApiClient(object):
def __init__(self, base_url, passwd=None):
@ -135,6 +166,8 @@ def get_args():
parser = argparse.ArgumentParser(description="photo library cli")
# TODO nicer uri parser
parser.add_argument("--host", default=os.environ.get("PHOTOLIB_URI", None), help="photo api server address")
parser.add_argument("--context", help="api server context defined in config file",
default=os.environ.get("PHOTOLIB_CONTEXT", None))
parser.add_argument("-y", "--yes", action="store_true", help="assume yes for all prompts")
sp_action = parser.add_subparsers(dest="action", help="action to take")
@ -189,20 +222,91 @@ def get_args():
p_delete = p_useraction.add_parser('delete', help='delete users')
p_delete.add_argument("-u", "--username", help="username", required=True)
p_ctx = sp_action.add_parser("ctx", help="change server contexts")
p_ctx.add_argument("new_context", help="context name")
p_editctx = sp_action.add_parser("update-context", help="create/edit server contexts")
p_editctx.add_argument("context_name", help="context name to modify")
p_editctx.add_argument("--set-host", help="photo api server address")
p_editctx.add_argument("--delete", action="store_true", help="delete the context")
sp_action.add_parser("list-contexts", help="list available contexts")
return parser.parse_args(), parser
def main():
args, parser = get_args()
config = CliConfig(APPNAME)
uri = urlparse( or config["uri"])
# figure out the selected context - first the --context flag, then conf file, then "default"
context = args.context or config.context
# default context is "sticky" and remembers the last --host used
if and context == "default": # TODO move this after parsing
new_default = urlparse(
if not new_default.netloc:
parser.error("--host uri is invalid")
config.set_server("default", new_default.geturl())
if and context != "default":
parser.error("--host not allowed for non-default contexts. Use `ctx` to switch to the default context or use "
"`update-context --host HOST` to create a new context.")
# these commands manipulate the config file so we don't check it yet
# change or edit contexts
if args.action == "ctx":
except KeyError as ke:
parser.error("{}. Use `update-context` to define it.".format(ke.args[0]))
elif args.action == "update-context":
ctx = config.select_context(args.context_name)
except KeyError:
ctx = DEFAULT_CONFIG["contexts"]["default"]
if args.set_host:
new_uri = urlparse(args.set_host)
if not new_uri.netloc:
parser.error("--set-host uri is invalid")
ctx["uri"] = new_uri.geturl()
if args.delete:
print("Context deleted")
except KeyError:
if not ctx["uri"]:
parser.error("New context must include a host, use `--set-host`.")
config.edit_context(args.context_name, ctx)
print("Context updated")
elif args.action == "list-contexts":
for entry in sorted(config["contexts"].keys()):
print("{}{}".format(entry, " (active)" if entry == context else ""))
# commands below here need a valid config file and api client
# validate the config
server_conf = config.select_context(context)
except KeyError as ke:
parser.error("{}. Use `update-context` to define it.".format(ke.args[0]))
uri = urlparse(server_conf["uri"])
if not uri.netloc:
parser.error("need --host, $PHOTOLIB_URI, or config file")
config["uri"] = uri.geturl()
# create the client
port = f":{uri.port}" if uri.port else ""
client = PhotoApiClient(f"{uri.scheme}://{uri.hostname}{port}", (uri.username, uri.password, ))