Add linting
This commit is contained in:
parent
8730e0974f
commit
b69b6155b3
|
@ -46,7 +46,10 @@ def repl(args):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
def process_doc(docstring):
|
def process_doc(docstring):
|
||||||
return '\n'.join([line.strip() for line in (docstring or "").strip().splitlines()])
|
return '\n'.join([
|
||||||
|
line.strip()
|
||||||
|
for line in (docstring or "").strip().splitlines()
|
||||||
|
])
|
||||||
|
|
||||||
def get_parser(valid_commands):
|
def get_parser(valid_commands):
|
||||||
# Set up the top-level parser.
|
# Set up the top-level parser.
|
||||||
|
@ -84,7 +87,10 @@ def get_parser(valid_commands):
|
||||||
metavar="USERNAME",
|
metavar="USERNAME",
|
||||||
dest="tl_username",
|
dest="tl_username",
|
||||||
help="Specify a user to operate on")
|
help="Specify a user to operate on")
|
||||||
parser.set_defaults(func=lambda args: repl(args) if args.tl_lexicon else parser.print_help())
|
parser.set_defaults(
|
||||||
|
func=lambda args: repl(args)
|
||||||
|
if args.tl_lexicon
|
||||||
|
else parser.print_help())
|
||||||
subp = parser.add_subparsers(
|
subp = parser.add_subparsers(
|
||||||
metavar="COMMAND",
|
metavar="COMMAND",
|
||||||
dest="command",
|
dest="command",
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
def server_commands(commands={}):
|
def server_commands(commands={}):
|
||||||
if commands: return commands
|
if commands:
|
||||||
|
return commands
|
||||||
import amanuensis.cli.server
|
import amanuensis.cli.server
|
||||||
for name, func in vars(amanuensis.cli.server).items():
|
for name, func in vars(amanuensis.cli.server).items():
|
||||||
if name.startswith("command_"):
|
if name.startswith("command_"):
|
||||||
|
@ -18,7 +19,8 @@ def server_commands(commands={}):
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
def lexicon_commands(commands={}):
|
def lexicon_commands(commands={}):
|
||||||
if commands: return commands
|
if commands:
|
||||||
|
return commands
|
||||||
import amanuensis.cli.lexicon
|
import amanuensis.cli.lexicon
|
||||||
for name, func in vars(amanuensis.cli.lexicon).items():
|
for name, func in vars(amanuensis.cli.lexicon).items():
|
||||||
if name.startswith("command_"):
|
if name.startswith("command_"):
|
||||||
|
@ -27,7 +29,8 @@ def lexicon_commands(commands={}):
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
def user_commands(commands={}):
|
def user_commands(commands={}):
|
||||||
if commands: return commands
|
if commands:
|
||||||
|
return commands
|
||||||
import amanuensis.cli.user
|
import amanuensis.cli.user
|
||||||
for name, func in vars(amanuensis.cli.user).items():
|
for name, func in vars(amanuensis.cli.user).items():
|
||||||
if name.startswith("command_"):
|
if name.startswith("command_"):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
|
|
||||||
# These function wrappers allow us to use the same function for executing a
|
# These function wrappers allow us to use the same function for executing a
|
||||||
# command and for configuring it. This keeps command arg configuration close to
|
# command and for configuring it. This keeps command arg configuration close to
|
||||||
|
@ -9,70 +11,108 @@ from functools import wraps
|
||||||
|
|
||||||
def add_argument(*args, **kwargs):
|
def add_argument(*args, **kwargs):
|
||||||
"""Passes the given args and kwargs to subparser.add_argument"""
|
"""Passes the given args and kwargs to subparser.add_argument"""
|
||||||
|
|
||||||
def argument_adder(command):
|
def argument_adder(command):
|
||||||
second_layer = command.__dict__.get('wrapper', False)
|
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def augmented_command(cmd_args):
|
def augmented_command(cmd_args):
|
||||||
if type(cmd_args) is ArgumentParser:
|
# Add this wrapper's command in the parser pass
|
||||||
|
if isinstance(cmd_args, ArgumentParser):
|
||||||
cmd_args.add_argument(*args, **kwargs)
|
cmd_args.add_argument(*args, **kwargs)
|
||||||
if type(cmd_args) is not ArgumentParser or second_layer:
|
# If there are more command wrappers, pass through to them
|
||||||
|
if command.__dict__.get('wrapper', False):
|
||||||
command(cmd_args)
|
command(cmd_args)
|
||||||
|
# Parser pass doesn't return a value
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Pass through transparently in the execute pass
|
||||||
|
return command(cmd_args)
|
||||||
|
|
||||||
|
# Mark the command as wrapped so control passes through
|
||||||
augmented_command.__dict__['wrapper'] = True
|
augmented_command.__dict__['wrapper'] = True
|
||||||
return augmented_command
|
return augmented_command
|
||||||
|
|
||||||
return argument_adder
|
return argument_adder
|
||||||
|
|
||||||
|
|
||||||
def no_argument(command):
|
def no_argument(command):
|
||||||
"""Noops for subparsers"""
|
"""Noops for subparsers"""
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def augmented_command(cmd_args):
|
def augmented_command(cmd_args):
|
||||||
if type(cmd_args) is not ArgumentParser:
|
# Noop in the parser pass
|
||||||
command(cmd_args)
|
if isinstance(cmd_args, ArgumentParser):
|
||||||
|
return None
|
||||||
|
# Pass through in the execute pass
|
||||||
|
return command(cmd_args)
|
||||||
|
|
||||||
return augmented_command
|
return augmented_command
|
||||||
|
|
||||||
|
|
||||||
# Wrappers for commands requiring lexicon or username options
|
# Wrappers for commands requiring lexicon or username options
|
||||||
|
|
||||||
def requires_lexicon(command):
|
def requires_lexicon(command):
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def augmented_command(cmd_args):
|
def augmented_command(cmd_args):
|
||||||
if type(cmd_args) is ArgumentParser:
|
# Add lexicon argument in parser pass
|
||||||
cmd_args.add_argument("-n", metavar="LEXICON", dest="lexicon", help="Specify a lexicon to operate on")
|
if isinstance(cmd_args, ArgumentParser):
|
||||||
|
cmd_args.add_argument(
|
||||||
|
"-n", metavar="LEXICON", dest="lexicon",
|
||||||
|
help="Specify a lexicon to operate on")
|
||||||
|
# If there are more command wrappers, pass through to them
|
||||||
if command.__dict__.get('wrapper', False):
|
if command.__dict__.get('wrapper', False):
|
||||||
command(cmd_args)
|
command(cmd_args)
|
||||||
if type(cmd_args) is Namespace:
|
# Parser pass doesn't return a value
|
||||||
base_val = hasattr(cmd_args, "tl_lexicon") and getattr(cmd_args, "tl_lexicon")
|
return None
|
||||||
subp_val = hasattr(cmd_args, "lexicon") and getattr(cmd_args, "lexicon")
|
|
||||||
|
# Verify lexicon argument in execute pass
|
||||||
|
base_val = (hasattr(cmd_args, "tl_lexicon")
|
||||||
|
and getattr(cmd_args, "tl_lexicon"))
|
||||||
|
subp_val = (hasattr(cmd_args, "lexicon")
|
||||||
|
and getattr(cmd_args, "lexicon"))
|
||||||
val = subp_val or base_val or None
|
val = subp_val or base_val or None
|
||||||
if not val:
|
if not val:
|
||||||
from amanuensis.config import logger
|
from amanuensis.config import logger
|
||||||
logger.error("This command requires specifying a lexicon")
|
logger.error("This command requires specifying a lexicon")
|
||||||
return -1
|
return -1
|
||||||
from amanuensis.lexicon import LexiconModel
|
# from amanuensis.lexicon import LexiconModel
|
||||||
cmd_args.lexicon = val#LexiconModel.by(name=val).name
|
cmd_args.lexicon = val#LexiconModel.by(name=val).name TODO
|
||||||
command(cmd_args)
|
return command(cmd_args)
|
||||||
|
|
||||||
augmented_command.__dict__['wrapper'] = True
|
augmented_command.__dict__['wrapper'] = True
|
||||||
return augmented_command
|
return augmented_command
|
||||||
|
|
||||||
|
|
||||||
def requires_username(command):
|
def requires_username(command):
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def augmented_command(cmd_args):
|
def augmented_command(cmd_args):
|
||||||
if type(cmd_args) is ArgumentParser:
|
# Add user argument in parser pass
|
||||||
cmd_args.add_argument("-u", metavar="USERNAME", dest="username", help="Specify a user to operate on")
|
if isinstance(cmd_args, ArgumentParser):
|
||||||
|
cmd_args.add_argument(
|
||||||
|
"-u", metavar="USERNAME", dest="username",
|
||||||
|
help="Specify a user to operate on")
|
||||||
|
# If there are more command wrappers, pass through to them
|
||||||
if command.__dict__.get('wrapper', False):
|
if command.__dict__.get('wrapper', False):
|
||||||
command(cmd_args)
|
command(cmd_args)
|
||||||
if type(cmd_args) is Namespace:
|
# Parser pass doesn't return a value
|
||||||
base_val = hasattr(cmd_args, "tl_username") and getattr(cmd_args, "tl_lexicon")
|
return None
|
||||||
subp_val = hasattr(cmd_args, "username") and getattr(cmd_args, "username")
|
|
||||||
|
# Verify user argument in execute pass
|
||||||
|
base_val = (hasattr(cmd_args, "tl_username")
|
||||||
|
and getattr(cmd_args, "tl_lexicon"))
|
||||||
|
subp_val = (hasattr(cmd_args, "username")
|
||||||
|
and getattr(cmd_args, "username"))
|
||||||
val = subp_val or base_val or None
|
val = subp_val or base_val or None
|
||||||
if not val:
|
if not val:
|
||||||
from amanuensis.config import logger
|
from amanuensis.config import logger
|
||||||
logger.error("This command requires specifying a user")
|
logger.error("This command requires specifying a user")
|
||||||
return -1
|
return -1
|
||||||
from amanuensis.user import UserModel
|
# from amanuensis.user import UserModel
|
||||||
cmd_args.username = val#UserModel.by(name=val).name
|
cmd_args.username = val#UserModel.by(name=val).name TODO
|
||||||
command(cmd_args)
|
return command(cmd_args)
|
||||||
|
|
||||||
augmented_command.__dict__['wrapper'] = True
|
augmented_command.__dict__['wrapper'] = True
|
||||||
return augmented_command
|
return augmented_command
|
||||||
|
|
||||||
|
|
||||||
# Helpers for common command tasks
|
# Helpers for common command tasks
|
||||||
|
|
||||||
CONFIG_GET_ROOT_VALUE = object()
|
CONFIG_GET_ROOT_VALUE = object()
|
||||||
|
@ -96,6 +136,7 @@ def config_get(cfg, pathspec):
|
||||||
return -1
|
return -1
|
||||||
cfg = cfg.get(spec)
|
cfg = cfg.get(spec)
|
||||||
print(json.dumps(cfg, indent=2))
|
print(json.dumps(cfg, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
def config_set(obj_id, cfg, set_tuple):
|
def config_set(obj_id, cfg, set_tuple):
|
||||||
"""
|
"""
|
||||||
|
@ -112,7 +153,7 @@ def config_set(obj_id, cfg, set_tuple):
|
||||||
path = pathspec.split('.')
|
path = pathspec.split('.')
|
||||||
try:
|
try:
|
||||||
value = json.loads(value)
|
value = json.loads(value)
|
||||||
except:
|
except JSONDecodeError:
|
||||||
pass # Leave value as string
|
pass # Leave value as string
|
||||||
for spec in path[:-1]:
|
for spec in path[:-1]:
|
||||||
if spec not in cfg:
|
if spec not in cfg:
|
||||||
|
@ -126,3 +167,4 @@ def config_set(obj_id, cfg, set_tuple):
|
||||||
old_value = cfg[key]
|
old_value = cfg[key]
|
||||||
cfg[key] = value
|
cfg[key] = value
|
||||||
logger.info("{}.{}: {} -> {}".format(obj_id, pathspec, old_value, value))
|
logger.info("{}.{}: {} -> {}".format(obj_id, pathspec, old_value, value))
|
||||||
|
return 0
|
||||||
|
|
|
@ -7,7 +7,9 @@ from amanuensis.cli.helpers import (
|
||||||
#
|
#
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
@add_argument("--editor", "-e", required=True, help="Name of the user who will be editor")
|
@add_argument(
|
||||||
|
"--editor", "-e", required=True,
|
||||||
|
help="Name of the user who will be editor")
|
||||||
def command_create(args):
|
def command_create(args):
|
||||||
"""
|
"""
|
||||||
Create a lexicon
|
Create a lexicon
|
||||||
|
@ -33,6 +35,7 @@ def command_create(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
create_lexicon(args.lexicon, editor)
|
create_lexicon(args.lexicon, editor)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -55,6 +58,7 @@ def command_delete(args):
|
||||||
# Internal call
|
# Internal call
|
||||||
delete_lexicon(lex, args.purge)
|
delete_lexicon(lex, args.purge)
|
||||||
logger.info("Deleted lexicon '{}'".format(args.lexicon))
|
logger.info("Deleted lexicon '{}'".format(args.lexicon))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@no_argument
|
@no_argument
|
||||||
|
@ -74,9 +78,11 @@ def command_list(args):
|
||||||
elif lex.turn['current'] > lex.turn['max']:
|
elif lex.turn['current'] > lex.turn['max']:
|
||||||
statuses.append("{0.lid} {0.name} ({1})".format(lex, "Completed"))
|
statuses.append("{0.lid} {0.name} ({1})".format(lex, "Completed"))
|
||||||
else:
|
else:
|
||||||
statuses.append("{0.lid} {0.name} (Turn {1}/{2})".format(lex, lex.turn['current'], lex.turn['max']))
|
statuses.append("{0.lid} {0.name} (Turn {1}/{2})".format(
|
||||||
|
lex, lex.turn['current'], lex.turn['max']))
|
||||||
for s in statuses:
|
for s in statuses:
|
||||||
print(s)
|
print(s)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -112,6 +118,8 @@ def command_config(args):
|
||||||
with json_rw(lex.config_path) as cfg:
|
with json_rw(lex.config_path) as cfg:
|
||||||
config_set(lex.id, cfg, args.set)
|
config_set(lex.id, cfg, args.set)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
#
|
#
|
||||||
# Player/character commands
|
# Player/character commands
|
||||||
#
|
#
|
||||||
|
@ -140,6 +148,7 @@ def command_player_add(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
add_player(lex, u)
|
add_player(lex, u)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -172,6 +181,7 @@ def command_player_remove(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
remove_player(lex, u)
|
remove_player(lex, u)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -181,6 +191,7 @@ def command_player_list(args):
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
# Module imports
|
# Module imports
|
||||||
|
from amanuensis.config import logger
|
||||||
from amanuensis.lexicon import LexiconModel
|
from amanuensis.lexicon import LexiconModel
|
||||||
from amanuensis.user import UserModel
|
from amanuensis.user import UserModel
|
||||||
|
|
||||||
|
@ -197,6 +208,7 @@ def command_player_list(args):
|
||||||
players.append(u.username)
|
players.append(u.username)
|
||||||
|
|
||||||
print(json.dumps(players, indent=2))
|
print(json.dumps(players, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -227,6 +239,7 @@ def command_char_create(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
add_character(lex, u, {"name": args.charname})
|
add_character(lex, u, {"name": args.charname})
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
|
@ -251,6 +264,7 @@ def command_char_delete(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
delete_character(lex, args.charname)
|
delete_character(lex, args.charname)
|
||||||
|
return 0
|
||||||
|
|
||||||
@requires_lexicon
|
@requires_lexicon
|
||||||
def command_char_list(args):
|
def command_char_list(args):
|
||||||
|
@ -259,6 +273,7 @@ def command_char_list(args):
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
# Module imports
|
# Module imports
|
||||||
|
from amanuensis.config import logger
|
||||||
from amanuensis.lexicon import LexiconModel
|
from amanuensis.lexicon import LexiconModel
|
||||||
|
|
||||||
# Verify arguments
|
# Verify arguments
|
||||||
|
@ -269,6 +284,7 @@ def command_char_list(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
print(json.dumps(lex.character, indent=2))
|
print(json.dumps(lex.character, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
#
|
#
|
||||||
# Procedural commands
|
# Procedural commands
|
||||||
|
@ -292,6 +308,5 @@ def command_publish_turn(args):
|
||||||
settings.
|
settings.
|
||||||
"""
|
"""
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import logger
|
|
||||||
|
|
||||||
raise NotImplementedError() # TODO
|
raise NotImplementedError() # TODO
|
|
@ -28,6 +28,7 @@ def command_init(args):
|
||||||
|
|
||||||
# Internal call
|
# Internal call
|
||||||
create_config_dir(args.config_dir, args.refresh)
|
create_config_dir(args.config_dir, args.refresh)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@no_argument
|
@no_argument
|
||||||
|
@ -38,7 +39,6 @@ def command_generate_secret(args):
|
||||||
The Flask server will not run unless a secret key has
|
The Flask server will not run unless a secret key has
|
||||||
been generated.
|
been generated.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import json_rw, logger
|
from amanuensis.config import json_rw, logger
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ def command_generate_secret(args):
|
||||||
with json_rw("config.json") as cfg:
|
with json_rw("config.json") as cfg:
|
||||||
cfg['secret_key'] = secret_key.hex()
|
cfg['secret_key'] = secret_key.hex()
|
||||||
logger.info("Regenerated Flask secret key")
|
logger.info("Regenerated Flask secret key")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@add_argument("-a", "--address", default="127.0.0.1")
|
@add_argument("-a", "--address", default="127.0.0.1")
|
||||||
|
@ -62,9 +63,11 @@ def command_run(args):
|
||||||
from amanuensis.config import get, logger
|
from amanuensis.config import get, logger
|
||||||
|
|
||||||
if get("secret_key") is None:
|
if get("secret_key") is None:
|
||||||
logger.error("Can't run server without a secret_key. Run generate-secret first")
|
logger.error("Can't run server without a secret_key. Run generate-sec"
|
||||||
|
"ret first")
|
||||||
return -1
|
return -1
|
||||||
app.run(host=args.address, port=args.port, debug=args.debug)
|
app.run(host=args.address, port=args.port, debug=args.debug)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@add_argument("--get", metavar="PATHSPEC", dest="get",
|
@add_argument("--get", metavar="PATHSPEC", dest="get",
|
||||||
|
@ -78,7 +81,6 @@ def command_config(args):
|
||||||
PATHSPEC is a path into the config object formatted as
|
PATHSPEC is a path into the config object formatted as
|
||||||
a dot-separated sequence of keys.
|
a dot-separated sequence of keys.
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import json_ro, json_rw, logger
|
from amanuensis.config import json_ro, json_rw, logger
|
||||||
|
|
||||||
|
@ -93,3 +95,5 @@ def command_config(args):
|
||||||
if args.set:
|
if args.set:
|
||||||
with json_rw('config.json') as cfg:
|
with json_rw('config.json') as cfg:
|
||||||
config_set("config", cfg, args.set)
|
config_set("config", cfg, args.set)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
|
@ -14,11 +14,13 @@ def command_create(args):
|
||||||
import json
|
import json
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import logger, json_ro
|
from amanuensis.config import logger, json_ro
|
||||||
from amanuensis.user import UserModel, valid_username, valid_email, create_user
|
from amanuensis.user import (
|
||||||
|
UserModel, valid_username, valid_email, create_user)
|
||||||
|
|
||||||
# Verify or query parameters
|
# Verify or query parameters
|
||||||
if not valid_username(args.username):
|
if not valid_username(args.username):
|
||||||
logger.error("Invalid username: usernames may only contain alphanumeric characters, dashes, and underscores")
|
logger.error("Invalid username: usernames may only contain alphanumer"
|
||||||
|
"ic characters, dashes, and underscores")
|
||||||
return -1
|
return -1
|
||||||
if UserModel.by(name=args.username) is not None:
|
if UserModel.by(name=args.username) is not None:
|
||||||
logger.error("Invalid username: username is already taken")
|
logger.error("Invalid username: username is already taken")
|
||||||
|
@ -33,7 +35,10 @@ def command_create(args):
|
||||||
new_user, tmp_pw = create_user(args.username, args.displayname, args.email)
|
new_user, tmp_pw = create_user(args.username, args.displayname, args.email)
|
||||||
with json_ro(new_user.config_path) as js:
|
with json_ro(new_user.config_path) as js:
|
||||||
print(json.dumps(js, indent=2))
|
print(json.dumps(js, indent=2))
|
||||||
print("Username: {}\nUser ID: {}\nPassword: {}".format(args.username, new_user.uid, tmp_pw))
|
print("Username: {}\nUser ID: {}\nPassword: {}".format(
|
||||||
|
args.username, new_user.uid, tmp_pw))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@add_argument("--id", required=True, help="id of user to delete")
|
@add_argument("--id", required=True, help="id of user to delete")
|
||||||
def command_delete(args):
|
def command_delete(args):
|
||||||
|
@ -42,20 +47,21 @@ def command_delete(args):
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import logger, prepend
|
from amanuensis.config import logger, prepend, json_rw
|
||||||
|
|
||||||
user_path = prepend('user', args.id)
|
user_path = prepend('user', args.id)
|
||||||
if not os.path.isdir(user_path):
|
if not os.path.isdir(user_path):
|
||||||
logger.error("No user with that id")
|
logger.error("No user with that id")
|
||||||
return -1
|
return -1
|
||||||
else:
|
|
||||||
shutil.rmtree(user_path)
|
shutil.rmtree(user_path)
|
||||||
with json_rw('user', 'index.json') as j:
|
with json_rw('user', 'index.json') as j:
|
||||||
if args.id in j:
|
if args.id in j: # TODO this is wrong
|
||||||
del j[uid]
|
del j[args.id]
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@no_argument
|
@no_argument
|
||||||
def command_list(args):
|
def command_list(args):
|
||||||
"""List all users"""
|
"""List all users"""
|
||||||
|
@ -66,12 +72,16 @@ def command_list(args):
|
||||||
user_dirs = os.listdir(prepend('user'))
|
user_dirs = os.listdir(prepend('user'))
|
||||||
users = []
|
users = []
|
||||||
for uid in user_dirs:
|
for uid in user_dirs:
|
||||||
if uid == "index.json": continue
|
if uid == "index.json":
|
||||||
|
continue
|
||||||
with json_ro('user', uid, 'config.json') as user:
|
with json_ro('user', uid, 'config.json') as user:
|
||||||
users.append(user)
|
users.append(user)
|
||||||
users.sort(key=lambda u: u['username'])
|
users.sort(key=lambda u: u['username'])
|
||||||
for user in users:
|
for user in users:
|
||||||
print("{0} {1} ({2})".format(user['uid'], user['displayname'], user['username']))
|
print("{0} {1} ({2})".format(
|
||||||
|
user['uid'], user['displayname'], user['username']))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@requires_username
|
@requires_username
|
||||||
@add_argument(
|
@add_argument(
|
||||||
|
@ -84,7 +94,6 @@ def command_config(args):
|
||||||
"""
|
"""
|
||||||
Interact with a user's config
|
Interact with a user's config
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import logger, json_ro, json_rw
|
from amanuensis.config import logger, json_ro, json_rw
|
||||||
from amanuensis.user import UserModel
|
from amanuensis.user import UserModel
|
||||||
|
@ -106,6 +115,8 @@ def command_config(args):
|
||||||
with json_rw('user', u.id, 'config.json') as cfg:
|
with json_rw('user', u.id, 'config.json') as cfg:
|
||||||
config_set(u.id, cfg, args.set)
|
config_set(u.id, cfg, args.set)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@add_argument("--username", help="The user to change password for")
|
@add_argument("--username", help="The user to change password for")
|
||||||
@add_argument("--password", help="The password to set. Not recommended")
|
@add_argument("--password", help="The password to set. Not recommended")
|
||||||
def command_passwd(args):
|
def command_passwd(args):
|
||||||
|
@ -113,7 +124,6 @@ def command_passwd(args):
|
||||||
Set a user's password
|
Set a user's password
|
||||||
"""
|
"""
|
||||||
import getpass
|
import getpass
|
||||||
import os
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.config import logger
|
from amanuensis.config import logger
|
||||||
from amanuensis.user import UserModel
|
from amanuensis.user import UserModel
|
||||||
|
@ -126,3 +136,5 @@ def command_passwd(args):
|
||||||
return -1
|
return -1
|
||||||
pw = args.password or getpass.getpass("Password: ")
|
pw = args.password or getpass.getpass("Password: ")
|
||||||
u.set_password(pw)
|
u.set_password(pw)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
|
@ -32,7 +32,8 @@ def init_config(args):
|
||||||
global CONFIG_DIR, GLOBAL_CONFIG, logger
|
global CONFIG_DIR, GLOBAL_CONFIG, logger
|
||||||
CONFIG_DIR = args.config_dir
|
CONFIG_DIR = args.config_dir
|
||||||
amanuensis.config.init.verify_config_dir(CONFIG_DIR)
|
amanuensis.config.init.verify_config_dir(CONFIG_DIR)
|
||||||
with amanuensis.config.loader.json_ro(os.path.join(CONFIG_DIR, "config.json")) as cfg:
|
with amanuensis.config.loader.json_ro(
|
||||||
|
os.path.join(CONFIG_DIR, "config.json")) as cfg:
|
||||||
GLOBAL_CONFIG = cfg
|
GLOBAL_CONFIG = cfg
|
||||||
amanuensis.config.init.init_logging(args, GLOBAL_CONFIG['logging'])
|
amanuensis.config.init.init_logging(args, GLOBAL_CONFIG['logging'])
|
||||||
logger = logging.getLogger("amanuensis")
|
logger = logging.getLogger("amanuensis")
|
||||||
|
|
|
@ -114,14 +114,17 @@ def verify_config_dir(config_dir):
|
||||||
# Check that global config file exists
|
# Check that global config file exists
|
||||||
global_config_path = os.path.join(config_dir, "config.json")
|
global_config_path = os.path.join(config_dir, "config.json")
|
||||||
if not os.path.isfile(global_config_path):
|
if not os.path.isfile(global_config_path):
|
||||||
raise MissingConfigError("Config directory missing global config file: {}".format(config_dir))
|
raise MissingConfigError("Config directory missing global config file"
|
||||||
|
": {}".format(config_dir))
|
||||||
# Check that global config file has all the default settings
|
# Check that global config file has all the default settings
|
||||||
def_cfg_s = get_stream("global.json")
|
def_cfg_s = get_stream("global.json")
|
||||||
def_cfg = json.load(def_cfg_s)
|
def_cfg = json.load(def_cfg_s)
|
||||||
with json_ro(global_config_path) as global_config_file:
|
with json_ro(global_config_path) as global_config_file:
|
||||||
for key in def_cfg.keys():
|
for key in def_cfg.keys():
|
||||||
if key not in global_config_file.keys():
|
if key not in global_config_file.keys():
|
||||||
raise MalformedConfigError("Missing '{}' in global config. If you updated Amanuensis, run init --refresh to pick up new config keys".format(key))
|
raise MalformedConfigError("Missing '{}' in global config. If"
|
||||||
|
" you updated Amanuensis, run init --refresh to pick up n"
|
||||||
|
"ew config keys".format(key))
|
||||||
# Configs verified
|
# Configs verified
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -147,4 +150,3 @@ def init_logging(args, logging_config):
|
||||||
logging.config.dictConfig(cfg)
|
logging.config.dictConfig(cfg)
|
||||||
except:
|
except:
|
||||||
raise MalformedConfigError("Failed to load logging config")
|
raise MalformedConfigError("Failed to load logging config")
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import fcntl
|
import fcntl
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from amanuensis.errors import ReadOnlyError
|
from amanuensis.errors import ReadOnlyError
|
||||||
|
@ -90,4 +89,3 @@ class json_rw(open_ex):
|
||||||
json.dump(self.config, self.fd, allow_nan=False, indent='\t')
|
json.dump(self.config, self.fd, allow_nan=False, indent='\t')
|
||||||
self.fd.truncate()
|
self.fd.truncate()
|
||||||
super().__exit__(exc_type, exc_value, traceback)
|
super().__exit__(exc_type, exc_value, traceback)
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
class AmanuensisError(Exception):
|
class AmanuensisError(Exception):
|
||||||
"""Base class for exceptions in amanuensis"""
|
"""Base class for exceptions in amanuensis"""
|
||||||
pass
|
|
||||||
|
|
||||||
class MissingConfigError(AmanuensisError):
|
class MissingConfigError(AmanuensisError):
|
||||||
"""A config file is missing that was expected to be present"""
|
"""A config file is missing that was expected to be present"""
|
||||||
pass
|
|
||||||
|
|
||||||
class MalformedConfigError(AmanuensisError):
|
class MalformedConfigError(AmanuensisError):
|
||||||
"""A config file could not be read and parsed"""
|
"""A config file could not be read and parsed"""
|
||||||
pass
|
|
||||||
|
|
||||||
class ReadOnlyError(AmanuensisError):
|
class ReadOnlyError(AmanuensisError):
|
||||||
"""A config was edited in readonly mode"""
|
"""A config was edited in readonly mode"""
|
||||||
pass
|
|
||||||
|
|
||||||
class InternalMisuseError(AmanuensisError):
|
class InternalMisuseError(AmanuensisError):
|
||||||
"""An internal helper method was called wrongly"""
|
"""An internal helper method was called wrongly"""
|
||||||
pass
|
|
||||||
|
|
||||||
class IndexMismatchError(AmanuensisError):
|
class IndexMismatchError(AmanuensisError):
|
||||||
"""An id was obtained from an index, but the object doesn't exist"""
|
"""An id was obtained from an index, but the object doesn't exist"""
|
||||||
pass
|
|
|
@ -1,10 +1,12 @@
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from amanuensis.errors import InternalMisuseError, IndexMismatchError, MissingConfigError
|
from amanuensis.errors import (
|
||||||
|
InternalMisuseError, IndexMismatchError, MissingConfigError)
|
||||||
from amanuensis.config import prepend, json_ro, json_rw
|
from amanuensis.config import prepend, json_ro, json_rw
|
||||||
|
|
||||||
class LexiconModel():
|
class LexiconModel():
|
||||||
|
@staticmethod
|
||||||
def by(lid=None, name=None):
|
def by(lid=None, name=None):
|
||||||
"""
|
"""
|
||||||
Gets the LexiconModel with the given lid or username
|
Gets the LexiconModel with the given lid or username
|
||||||
|
@ -14,7 +16,8 @@ class LexiconModel():
|
||||||
the lexicon's config, raises an error.
|
the lexicon's config, raises an error.
|
||||||
"""
|
"""
|
||||||
if lid and name:
|
if lid and name:
|
||||||
raise InternalMisuseError("lid and name both specified to LexiconModel.by()")
|
raise InternalMisuseError("lid and name both specified to Lexicon"
|
||||||
|
"Model.by()")
|
||||||
if not lid and not name:
|
if not lid and not name:
|
||||||
raise ValueError("One of lid or name must be not None")
|
raise ValueError("One of lid or name must be not None")
|
||||||
if not lid:
|
if not lid:
|
||||||
|
@ -51,7 +54,6 @@ class LexiconModel():
|
||||||
def status(self):
|
def status(self):
|
||||||
if self.turn.current is None:
|
if self.turn.current is None:
|
||||||
return "unstarted"
|
return "unstarted"
|
||||||
elif self.turn.current > self.turn.max:
|
if self.turn.current > self.turn.max:
|
||||||
return "completed"
|
return "completed"
|
||||||
else:
|
|
||||||
return "ongoing"
|
return "ongoing"
|
|
@ -166,7 +166,9 @@ def remove_player(lex, player):
|
||||||
if player is None:
|
if player is None:
|
||||||
raise ValueError("Invalid player: '{}'".format(player))
|
raise ValueError("Invalid player: '{}'".format(player))
|
||||||
if lex.editor == player.id:
|
if lex.editor == player.id:
|
||||||
raise ValueError("Can't remove the editor '{}' from lexicon '{}'".format(player.username, lex.name))
|
raise ValueError(
|
||||||
|
"Can't remove the editor '{}' from lexicon '{}'".format(
|
||||||
|
player.username, lex.name))
|
||||||
|
|
||||||
# Idempotently remove player
|
# Idempotently remove player
|
||||||
with json_rw(lex.config_path) as cfg:
|
with json_rw(lex.config_path) as cfg:
|
||||||
|
@ -190,7 +192,9 @@ def add_character(lex, player, charinfo={}):
|
||||||
if not charinfo or not charinfo.get("name"):
|
if not charinfo or not charinfo.get("name"):
|
||||||
raise ValueError("Invalid character info: '{}'".format(charinfo))
|
raise ValueError("Invalid character info: '{}'".format(charinfo))
|
||||||
charname = charinfo.get("name")
|
charname = charinfo.get("name")
|
||||||
if any([char.name for char in lex.character.values() if char.name == charname]):
|
if any([
|
||||||
|
char.name for char in lex.character.values()
|
||||||
|
if char.name == charname]):
|
||||||
raise ValueError("Duplicate character name: '{}'".format(charinfo))
|
raise ValueError("Duplicate character name: '{}'".format(charinfo))
|
||||||
|
|
||||||
# Load the character template
|
# Load the character template
|
||||||
|
@ -221,10 +225,12 @@ def delete_character(lex, charname):
|
||||||
if lex is None:
|
if lex is None:
|
||||||
raise ValueError("Invalid lexicon: '{}'".format(lex))
|
raise ValueError("Invalid lexicon: '{}'".format(lex))
|
||||||
if charname is None:
|
if charname is None:
|
||||||
raise ValueError("Invalid character name: '{}'".format(charinfo))
|
raise ValueError("Invalid character name: '{}'".format(charname))
|
||||||
|
|
||||||
# Find character in this lexicon
|
# Find character in this lexicon
|
||||||
matches = [char for cid, char in lex.character.items() if char.name == charname]
|
matches = [
|
||||||
|
char for cid, char in lex.character.items()
|
||||||
|
if char.name == charname]
|
||||||
if len(matches) != 1:
|
if len(matches) != 1:
|
||||||
raise ValueError(matches)
|
raise ValueError(matches)
|
||||||
char = matches[0]
|
char = matches[0]
|
||||||
|
|
|
@ -11,7 +11,10 @@ from amanuensis.server.lexicon import get_bp as get_lex_bp
|
||||||
|
|
||||||
# Flask app init
|
# Flask app init
|
||||||
static_root = os.path.abspath(get("static_root"))
|
static_root = os.path.abspath(get("static_root"))
|
||||||
app = Flask(__name__, template_folder="../templates", static_folder=static_root)
|
app = Flask(
|
||||||
|
__name__,
|
||||||
|
template_folder="../templates",
|
||||||
|
static_folder=static_root)
|
||||||
app.secret_key = bytes.fromhex(get('secret_key'))
|
app.secret_key = bytes.fromhex(get('secret_key'))
|
||||||
app.jinja_options['trim_blocks'] = True
|
app.jinja_options['trim_blocks'] = True
|
||||||
app.jinja_options['lstrip_blocks'] = True
|
app.jinja_options['lstrip_blocks'] = True
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
|
from wtforms import (
|
||||||
|
StringField, PasswordField, BooleanField, SubmitField, TextAreaField)
|
||||||
from wtforms.validators import DataRequired, ValidationError
|
from wtforms.validators import DataRequired, ValidationError
|
||||||
|
|
||||||
from amanuensis.config import json_ro
|
from amanuensis.config import json_ro
|
||||||
|
@ -7,7 +8,8 @@ from amanuensis.config import json_ro
|
||||||
|
|
||||||
# Custom validators
|
# Custom validators
|
||||||
def user(exists=True):
|
def user(exists=True):
|
||||||
template = 'User "{{}}" {}'.format("not found" if exists else "already exists")
|
template = 'User "{{}}" {}'.format(
|
||||||
|
"not found" if exists else "already exists")
|
||||||
should_exist = bool(exists)
|
should_exist = bool(exists)
|
||||||
def validate_user(form, field):
|
def validate_user(form, field):
|
||||||
with json_ro('user', 'index.json') as index:
|
with json_ro('user', 'index.json') as index:
|
||||||
|
@ -17,7 +19,8 @@ def user(exists=True):
|
||||||
|
|
||||||
|
|
||||||
def lexicon(exists=True):
|
def lexicon(exists=True):
|
||||||
template = 'Lexicon "{{}}" {}'.format("not found" if exists else "already exists")
|
template = 'Lexicon "{{}}" {}'.format(
|
||||||
|
"not found" if exists else "already exists")
|
||||||
should_exist = bool(exists)
|
should_exist = bool(exists)
|
||||||
def validate_lexicon(form, field):
|
def validate_lexicon(form, field):
|
||||||
with json_ro('lexicon', 'index.json') as index:
|
with json_ro('lexicon', 'index.json') as index:
|
||||||
|
@ -37,8 +40,12 @@ class LoginForm(FlaskForm):
|
||||||
|
|
||||||
class LexiconCreateForm(FlaskForm):
|
class LexiconCreateForm(FlaskForm):
|
||||||
"""/admin/create/"""
|
"""/admin/create/"""
|
||||||
lexiconName = StringField('Lexicon name', validators=[DataRequired(), lexicon(exists=False)])
|
lexiconName = StringField(
|
||||||
editorName = StringField('Username of editor', validators=[DataRequired(), user(exists=True)])
|
'Lexicon name',
|
||||||
|
validators=[DataRequired(), lexicon(exists=False)])
|
||||||
|
editorName = StringField(
|
||||||
|
'Username of editor',
|
||||||
|
validators=[DataRequired(), user(exists=True)])
|
||||||
promptText = TextAreaField("Prompt")
|
promptText = TextAreaField("Prompt")
|
||||||
submit = SubmitField('Create')
|
submit = SubmitField('Create')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
from functools import wraps
|
from flask import Blueprint, render_template
|
||||||
import json
|
from flask_login import login_required
|
||||||
|
|
||||||
from flask import Blueprint, render_template, url_for, redirect, flash
|
|
||||||
from flask_login import login_required, current_user
|
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from wtforms import TextAreaField, SubmitField, StringField
|
|
||||||
|
|
||||||
from amanuensis.config import json_ro
|
from amanuensis.config import json_ro
|
||||||
from amanuensis.lexicon import LexiconModel
|
from amanuensis.lexicon import LexiconModel
|
||||||
|
@ -22,6 +17,7 @@ def get_bp():
|
||||||
return render_template('home/home.html')
|
return render_template('home/home.html')
|
||||||
|
|
||||||
@bp.route('/admin/', methods=['GET'])
|
@bp.route('/admin/', methods=['GET'])
|
||||||
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin():
|
def admin():
|
||||||
users = []
|
users = []
|
||||||
|
@ -37,6 +33,7 @@ def get_bp():
|
||||||
return render_template('home/admin.html', users=users, lexicons=lexicons)
|
return render_template('home/admin.html', users=users, lexicons=lexicons)
|
||||||
|
|
||||||
@bp.route("/admin/create/", methods=['GET', 'POST'])
|
@bp.route("/admin/create/", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_create():
|
def admin_create():
|
||||||
form = LexiconCreateForm()
|
form = LexiconCreateForm()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from functools import wraps
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import Blueprint, render_template, url_for, redirect, g, flash
|
from flask import Blueprint, render_template, url_for, redirect, g, flash
|
||||||
|
@ -6,10 +5,8 @@ from flask_login import login_required, current_user
|
||||||
|
|
||||||
from amanuensis.config import json_ro, open_ex
|
from amanuensis.config import json_ro, open_ex
|
||||||
from amanuensis.config.loader import ReadOnlyOrderedDict
|
from amanuensis.config.loader import ReadOnlyOrderedDict
|
||||||
from amanuensis.lexicon import LexiconModel
|
|
||||||
from amanuensis.server.forms import LexiconConfigForm
|
from amanuensis.server.forms import LexiconConfigForm
|
||||||
from amanuensis.server.helpers import lexicon_param, player_required
|
from amanuensis.server.helpers import lexicon_param
|
||||||
from amanuensis.user import UserModel
|
|
||||||
|
|
||||||
|
|
||||||
def get_bp():
|
def get_bp():
|
||||||
|
@ -54,8 +51,9 @@ def get_bp():
|
||||||
if form.validate():
|
if form.validate():
|
||||||
# Check input is valid json
|
# Check input is valid json
|
||||||
try:
|
try:
|
||||||
cfg = json.loads(form.configText.data, object_pairs_hook=ReadOnlyOrderedDict)
|
cfg = json.loads(form.configText.data,
|
||||||
except:
|
object_pairs_hook=ReadOnlyOrderedDict)
|
||||||
|
except json.decoder.JsonDecodeError:
|
||||||
flash("Invalid JSON")
|
flash("Invalid JSON")
|
||||||
return render_template("lexicon/settings.html", form=form)
|
return render_template("lexicon/settings.html", form=form)
|
||||||
# Check input has all the required fields
|
# Check input has all the required fields
|
||||||
|
|
|
@ -6,7 +6,8 @@ import uuid
|
||||||
from flask_login import UserMixin
|
from flask_login import UserMixin
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from amanuensis.errors import InternalMisuseError, MissingConfigError, IndexMismatchError
|
from amanuensis.errors import (
|
||||||
|
InternalMisuseError, MissingConfigError, IndexMismatchError)
|
||||||
from amanuensis.config import prepend, json_ro, json_rw
|
from amanuensis.config import prepend, json_ro, json_rw
|
||||||
from amanuensis.resources import get_stream
|
from amanuensis.resources import get_stream
|
||||||
from amanuensis.lexicon.manage import get_all_lexicons
|
from amanuensis.lexicon.manage import get_all_lexicons
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# pylint configuration
|
||||||
|
|
||||||
|
[MASTER]
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
disable=
|
||||||
|
bad-continuation,
|
||||||
|
broad-except,
|
||||||
|
dangerous-default-value,
|
||||||
|
duplicate-code,
|
||||||
|
fixme,
|
||||||
|
global-statement,
|
||||||
|
len-as-condition,
|
||||||
|
logging-format-interpolation,
|
||||||
|
import-outside-toplevel,
|
||||||
|
invalid-name,
|
||||||
|
missing-docstring,
|
||||||
|
mixed-indentation,
|
||||||
|
no-member,
|
||||||
|
no-self-use,
|
||||||
|
redefined-variable-type,
|
||||||
|
too-few-public-methods,
|
||||||
|
too-many-arguments,
|
||||||
|
too-many-branches,
|
||||||
|
too-many-instance-attributes,
|
||||||
|
too-many-lines,
|
||||||
|
too-many-locals,
|
||||||
|
too-many-public-methods,
|
||||||
|
too-many-return-statements,
|
||||||
|
too-many-statements,
|
||||||
|
unused-argument,
|
||||||
|
unused-variable,
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
max-line-length=79
|
|
@ -1,10 +1,18 @@
|
||||||
|
astroid==2.3.3
|
||||||
Click==7.0
|
Click==7.0
|
||||||
Flask==1.1.1
|
Flask==1.1.1
|
||||||
Flask-Login==0.4.1
|
Flask-Login==0.4.1
|
||||||
Flask-WTF==0.14.2
|
Flask-WTF==0.14.2
|
||||||
|
isort==4.3.21
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.10.3
|
Jinja2==2.10.3
|
||||||
|
lazy-object-proxy==1.4.3
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
|
mccabe==0.6.1
|
||||||
pkg-resources==0.0.0
|
pkg-resources==0.0.0
|
||||||
|
pylint==2.4.4
|
||||||
|
six==1.14.0
|
||||||
|
typed-ast==1.4.1
|
||||||
Werkzeug==0.16.0
|
Werkzeug==0.16.0
|
||||||
|
wrapt==1.11.2
|
||||||
WTForms==2.2.1
|
WTForms==2.2.1
|
||||||
|
|
Loading…
Reference in New Issue