Add linting

This commit is contained in:
Tim Van Baak 2020-01-27 12:30:40 -08:00
parent 8730e0974f
commit b69b6155b3
21 changed files with 249 additions and 113 deletions

View File

@ -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",

View File

@ -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_"):

View File

@ -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
command(cmd_args) if command.__dict__.get('wrapper', False):
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")
val = subp_val or base_val or None # Verify lexicon argument in execute pass
if not val: base_val = (hasattr(cmd_args, "tl_lexicon")
from amanuensis.config import logger and getattr(cmd_args, "tl_lexicon"))
logger.error("This command requires specifying a lexicon") subp_val = (hasattr(cmd_args, "lexicon")
return -1 and getattr(cmd_args, "lexicon"))
from amanuensis.lexicon import LexiconModel val = subp_val or base_val or None
cmd_args.lexicon = val#LexiconModel.by(name=val).name if not val:
command(cmd_args) from amanuensis.config import logger
logger.error("This command requires specifying a lexicon")
return -1
# from amanuensis.lexicon import LexiconModel
cmd_args.lexicon = val#LexiconModel.by(name=val).name TODO
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")
val = subp_val or base_val or None # Verify user argument in execute pass
if not val: base_val = (hasattr(cmd_args, "tl_username")
from amanuensis.config import logger and getattr(cmd_args, "tl_lexicon"))
logger.error("This command requires specifying a user") subp_val = (hasattr(cmd_args, "username")
return -1 and getattr(cmd_args, "username"))
from amanuensis.user import UserModel val = subp_val or base_val or None
cmd_args.username = val#UserModel.by(name=val).name if not val:
command(cmd_args) from amanuensis.config import logger
logger.error("This command requires specifying a user")
return -1
# from amanuensis.user import UserModel
cmd_args.username = val#UserModel.by(name=val).name TODO
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:
@ -125,4 +166,5 @@ def config_set(obj_id, cfg, set_tuple):
return -1 return -1
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

View File

@ -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

View File

@ -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")
@ -54,7 +55,7 @@ def command_generate_secret(args):
def command_run(args): def command_run(args):
""" """
Run the default Flask server Run the default Flask server
The default Flask server is not secure, and should The default Flask server is not secure, and should
only be used for development. only be used for development.
""" """
@ -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

View File

@ -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

View File

@ -10,11 +10,11 @@ import amanuensis.config.loader
# Environment variable name constants # Environment variable name constants
ENV_SECRET_KEY = "AMANUENSIS_SECRET_KEY" ENV_SECRET_KEY = "AMANUENSIS_SECRET_KEY"
ENV_CONFIG_DIR = "AMANUENSIS_CONFIG_DIR" ENV_CONFIG_DIR = "AMANUENSIS_CONFIG_DIR"
ENV_LOG_FILE = "AMANUENSIS_LOG_FILE" ENV_LOG_FILE = "AMANUENSIS_LOG_FILE"
ENV_LOG_FILE_SIZE = "AMANUENSIS_LOG_FILE_SIZE" ENV_LOG_FILE_SIZE = "AMANUENSIS_LOG_FILE_SIZE"
ENV_LOG_FILE_NUM = "AMANUENSIS_LOG_FILE_NUM" ENV_LOG_FILE_NUM = "AMANUENSIS_LOG_FILE_NUM"
# #
# The config directory can be set by cli input, so the config infrastructure # The config directory can be set by cli input, so the config infrastructure
@ -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")
@ -56,4 +57,4 @@ def json_ro(*path):
return amanuensis.config.loader.json_ro(prepend(*path)) return amanuensis.config.loader.json_ro(prepend(*path))
def json_rw(*path): def json_rw(*path):
return amanuensis.config.loader.json_rw(prepend(*path)) return amanuensis.config.loader.json_rw(prepend(*path))

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -80,7 +80,7 @@ def delete_lexicon(lex, purge=False):
# Verify arguments # Verify arguments
if lex is None: if lex is None:
raise ValueError("Invalid lexicon: '{}'".format(lex)) raise ValueError("Invalid lexicon: '{}'".format(lex))
# Delete the lexicon from the index # Delete the lexicon from the index
with json_rw('lexicon', 'index.json') as j: with json_rw('lexicon', 'index.json') as j:
if lex.id in j: if lex.id in j:
@ -150,7 +150,7 @@ def add_player(lex, player):
if player.id not in cfg.join.joined: if player.id not in cfg.join.joined:
cfg.join.joined.append(player.id) cfg.join.joined.append(player.id)
added = True added = True
# Log to the lexicon's log # Log to the lexicon's log
if added: if added:
lex.log("Player '{0.username}' joined ({0.id})".format(player)) lex.log("Player '{0.username}' joined ({0.id})".format(player))
@ -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,14 +225,16 @@ 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]
# Remove character from character list # Remove character from character list
with json_rw(lex.config_path) as cfg: with json_rw(lex.config_path) as cfg:
del cfg.character[char.cid] del cfg.character[char.cid]

View File

@ -2,4 +2,4 @@ import pkg_resources
def get_stream(*path): def get_stream(*path):
rs_path = "/".join(path) rs_path = "/".join(path)
return pkg_resources.resource_stream(__name__, rs_path) return pkg_resources.resource_stream(__name__, rs_path)

View File

@ -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
@ -29,4 +32,4 @@ home_bp = get_home_bp()
app.register_blueprint(home_bp) app.register_blueprint(home_bp)
lex_bp = get_lex_bp() lex_bp = get_lex_bp()
app.register_blueprint(lex_bp) app.register_blueprint(lex_bp)

View File

@ -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')
@ -46,4 +53,4 @@ class LexiconCreateForm(FlaskForm):
class LexiconConfigForm(FlaskForm): class LexiconConfigForm(FlaskForm):
"""/lexicon/<name>/session/settings/""" """/lexicon/<name>/session/settings/"""
configText = TextAreaField("Config file") configText = TextAreaField("Config file")
submit = SubmitField("Submit") submit = SubmitField("Submit")

View File

@ -49,4 +49,4 @@ def player_required(route):
flash("You must be a player to view this page") flash("You must be a player to view this page")
return redirect(url_for('home.home')) return redirect(url_for('home.home'))
return route(*args, **kwargs) return route(*args, **kwargs)
return player_route return player_route

View File

@ -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()

View File

@ -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

View File

@ -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

37
pylintrc Normal file
View File

@ -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

View File

@ -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