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

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

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

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

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

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

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