Move all functionality under a single `worblehat` command
This commit is contained in:
parent
31184dde12
commit
fad38adc50
|
@ -1,9 +1,6 @@
|
|||
# See https://flask.palletsprojects.com/en/2.3.x/config/
|
||||
[flask]
|
||||
TESTING = true
|
||||
DEBUG = true
|
||||
FLASK_ENV = 'development'
|
||||
SECRET_KEY = 'change-me'
|
||||
[logging]
|
||||
debug = true
|
||||
debug_sql = false
|
||||
|
||||
[database]
|
||||
# One of (sqlite, postgres)
|
||||
|
@ -16,5 +13,13 @@ path = './worblehat.sqlite'
|
|||
host = 'localhost'
|
||||
port = 5432
|
||||
username = 'worblehat'
|
||||
password = 'change-me'
|
||||
password = '/var/lib/worblehat/db-password' # path or plain text
|
||||
name = 'worblehat'
|
||||
|
||||
# See https://flask.palletsprojects.com/en/2.3.x/config/
|
||||
[flask]
|
||||
TESTING = true
|
||||
DEBUG = true
|
||||
FLASK_ENV = 'development'
|
||||
SECRET_KEY = 'change-me' # path or plain text
|
||||
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
inherit program;
|
||||
};
|
||||
in {
|
||||
default = self.apps.${system}.dev;
|
||||
dev = app "${self.packages.${system}.worblehat}/bin/dev";
|
||||
cli = app "${self.packages.${system}.worblehat}/bin/cli";
|
||||
default = self.apps.${system}.worblehat;
|
||||
worblehat = app "${self.packages.${system}.worblehat}/bin/worblehat";
|
||||
};
|
||||
|
||||
packages.${system} = {
|
||||
|
|
|
@ -22,8 +22,7 @@ werkzeug = "^2.3.3"
|
|||
poethepoet = "^0.20.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
cli = "worblehat.cli.main:main"
|
||||
dev = "worblehat.flaskapp.wsgi_dev:main"
|
||||
worblehat = "worblehat.main:main"
|
||||
|
||||
[tool.poe.tasks]
|
||||
clean = """
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .main import WorblehatCli
|
|
@ -1,19 +1,15 @@
|
|||
from textwrap import dedent
|
||||
|
||||
from sqlalchemy import (
|
||||
create_engine,
|
||||
event,
|
||||
select,
|
||||
)
|
||||
from sqlalchemy.orm import (
|
||||
Session,
|
||||
)
|
||||
from worblehat.services.bookcase_item import (
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from worblehat.services import (
|
||||
create_bookcase_item_from_isbn,
|
||||
is_valid_isbn,
|
||||
)
|
||||
from worblehat.services.config import Config
|
||||
from worblehat.services.argument_parser import parse_args
|
||||
|
||||
from worblehat.models import *
|
||||
|
||||
|
@ -29,19 +25,10 @@ from .subclis import (
|
|||
# the shelves?
|
||||
|
||||
class WorblehatCli(NumberedCmd):
|
||||
sql_session: Session
|
||||
sql_session_dirty: bool = False
|
||||
|
||||
def __init__(self, args: dict[str, any] | None = None):
|
||||
def __init__(self, sql_session: Session):
|
||||
super().__init__()
|
||||
|
||||
try:
|
||||
engine = create_engine(Config.db_string(), echo=args.get('verbose_sql', False))
|
||||
self.sql_session = Session(engine)
|
||||
except Exception as err:
|
||||
print('Error: could not connect to database.')
|
||||
print(err)
|
||||
exit(1)
|
||||
self.sql_session = sql_session
|
||||
self.sql_session_dirty = False
|
||||
|
||||
@event.listens_for(self.sql_session, 'after_flush')
|
||||
def mark_session_as_dirty(*_):
|
||||
|
@ -54,7 +41,23 @@ class WorblehatCli(NumberedCmd):
|
|||
self.sql_session_dirty = False
|
||||
self.prompt_header = None
|
||||
|
||||
print(f"Debug: Connected to database at '{Config.db_string()}'")
|
||||
@classmethod
|
||||
def run_with_safe_exit_wrapper(cls, sql_session: Session):
|
||||
tool = cls(sql_session)
|
||||
while True:
|
||||
try:
|
||||
tool.cmdloop()
|
||||
except KeyboardInterrupt:
|
||||
if not tool.sql_session_dirty:
|
||||
exit(0)
|
||||
try:
|
||||
print()
|
||||
if prompt_yes_no('Are you sure you want to exit without saving?', default=False):
|
||||
raise KeyboardInterrupt
|
||||
except KeyboardInterrupt:
|
||||
if tool.sql_session is not None:
|
||||
tool.sql_session.rollback()
|
||||
exit(0)
|
||||
|
||||
|
||||
def do_list_bookcases(self, _: str):
|
||||
|
@ -222,27 +225,3 @@ class WorblehatCli(NumberedCmd):
|
|||
'doc': 'Exit',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
Config.load_configuration(args)
|
||||
|
||||
tool = WorblehatCli(args)
|
||||
while True:
|
||||
try:
|
||||
tool.cmdloop()
|
||||
except KeyboardInterrupt:
|
||||
if not tool.sql_session_dirty:
|
||||
exit(0)
|
||||
try:
|
||||
print()
|
||||
if prompt_yes_no('Are you sure you want to exit without saving?', default=False):
|
||||
raise KeyboardInterrupt
|
||||
except KeyboardInterrupt:
|
||||
if tool.sql_session is not None:
|
||||
tool.sql_session.rollback()
|
||||
exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -13,13 +13,10 @@ from .database import db
|
|||
def create_app(args: dict[str, any] | None = None):
|
||||
app = Flask(__name__)
|
||||
|
||||
if args is not None:
|
||||
Config.load_configuration(args)
|
||||
print(Config.db_string())
|
||||
app.config.update(Config['flask'])
|
||||
app.config.update(Config._config)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = Config.db_string()
|
||||
app.config['SQLALCHEMY_ECHO'] = args.get('verbose_sql')
|
||||
app.config.update(Config['flask'])
|
||||
app.config.update(Config._config)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = Config.db_string()
|
||||
app.config['SQLALCHEMY_ECHO'] = Config['logging.debug_sql']
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
from werkzeug import run_simple
|
||||
|
||||
from worblehat.services.config import Config
|
||||
from worblehat.services.argument_parser import parse_args
|
||||
|
||||
from .flaskapp import create_app
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
app = create_app(args)
|
||||
app = create_app()
|
||||
run_simple(
|
||||
hostname = 'localhost',
|
||||
port = 5000,
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
from .flaskapp import create_app
|
||||
|
||||
app = create_app()
|
||||
def main():
|
||||
app = create_app()
|
||||
app.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import logging
|
||||
from pprint import pformat
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .services import (
|
||||
Config,
|
||||
arg_parser,
|
||||
)
|
||||
|
||||
from .cli import WorblehatCli
|
||||
from .flaskapp.wsgi_dev import main as flask_dev_main
|
||||
from .flaskapp.wsgi_prod import main as flask_prod_main
|
||||
|
||||
|
||||
def _print_version() -> None:
|
||||
from worblehat import __version__
|
||||
print(f'Worblehat version {__version__}')
|
||||
|
||||
|
||||
def _connect_to_database(**engine_args) -> Session:
|
||||
try:
|
||||
engine = create_engine(Config.db_string(), **engine_args)
|
||||
sql_session = Session(engine)
|
||||
except Exception as err:
|
||||
print('Error: could not connect to database.')
|
||||
print(err)
|
||||
exit(1)
|
||||
|
||||
print(f"Debug: Connected to database at '{Config.db_string()}'")
|
||||
return sql_session
|
||||
|
||||
|
||||
def main():
|
||||
args = arg_parser.parse_args()
|
||||
Config.load_configuration(vars(args))
|
||||
|
||||
if Config['logging.debug']:
|
||||
logging.basicConfig(encoding='utf-8', level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(encoding='utf-8', level=logging.INFO)
|
||||
|
||||
if args.version:
|
||||
_print_version()
|
||||
exit(0)
|
||||
|
||||
if args.print_config:
|
||||
print(f'Configuration:\n{pformat(vars(args))}')
|
||||
exit(0)
|
||||
|
||||
if args.command == 'cli':
|
||||
sql_session = _connect_to_database(echo=Config['logging.debug_sql'])
|
||||
WorblehatCli.run_with_safe_exit_wrapper(sql_session)
|
||||
exit(0)
|
||||
|
||||
if args.command == 'flask-dev':
|
||||
flask_dev_main()
|
||||
exit(0)
|
||||
|
||||
if args.command == 'flask-prod':
|
||||
if Config['logging.debug'] or Config['logging.debug_sql']:
|
||||
logging.warn('Debug mode is enabled for the production server. This is not recommended.')
|
||||
flask_prod_main()
|
||||
exit(0)
|
||||
|
||||
print(arg_parser.format_help())
|
|
@ -0,0 +1,8 @@
|
|||
from .argument_parser import arg_parser
|
||||
from .bookcase_item import (
|
||||
create_bookcase_item_from_isbn,
|
||||
is_valid_isbn,
|
||||
)
|
||||
from .config import Config
|
||||
from .email import send_email
|
||||
from .seed_test_data import seed_data
|
|
@ -1,12 +1,5 @@
|
|||
from argparse import ArgumentParser
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
|
||||
def _print_version() -> None:
|
||||
from worblehat import __version__
|
||||
print(f'Worblehat version {__version__}')
|
||||
|
||||
|
||||
def _is_valid_file(parser: ArgumentParser, arg: str) -> Path:
|
||||
path = Path(arg)
|
||||
|
@ -16,47 +9,41 @@ def _is_valid_file(parser: ArgumentParser, arg: str) -> Path:
|
|||
return path
|
||||
|
||||
|
||||
def parse_args() -> dict[str, any]:
|
||||
parser = ArgumentParser(
|
||||
description = 'Worblehat library management system',
|
||||
)
|
||||
arg_parser = ArgumentParser(
|
||||
description = 'Worblehat library management system',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action = 'store_true',
|
||||
help = 'Enable verbose mode',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--verbose-sql',
|
||||
action = 'store_true',
|
||||
help = 'Enable verbose SQL mode',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
action = 'store_true',
|
||||
help = 'Print version and exit',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
type=lambda x: _is_valid_file(parser, x),
|
||||
help = 'Path to config file',
|
||||
dest = 'config_file',
|
||||
metavar = 'FILE',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--print-config',
|
||||
action = 'store_true',
|
||||
help = 'Print configuration and quit',
|
||||
)
|
||||
subparsers = arg_parser.add_subparsers(dest='command')
|
||||
subparsers.add_parser(
|
||||
'cli',
|
||||
help = 'Start the command line interface',
|
||||
)
|
||||
subparsers.add_parser(
|
||||
'flask-dev',
|
||||
help = 'Start the web interface in development mode',
|
||||
)
|
||||
subparsers.add_parser(
|
||||
'flask-prod',
|
||||
help = 'Start the web interface in production mode',
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
_print_version()
|
||||
exit(0)
|
||||
|
||||
if args.print_config:
|
||||
print(f'Configuration:\n{pformat(vars(args))}')
|
||||
exit(0)
|
||||
|
||||
return vars(args)
|
||||
arg_parser.add_argument(
|
||||
'-V',
|
||||
'--version',
|
||||
action = 'store_true',
|
||||
help = 'Print version and exit',
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
type=lambda x: _is_valid_file(arg_parser, x),
|
||||
help = 'Path to config file',
|
||||
dest = 'config_file',
|
||||
metavar = 'FILE',
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
'-p',
|
||||
'--print-config',
|
||||
action = 'store_true',
|
||||
help = 'Print configuration and quit',
|
||||
)
|
||||
|
|
|
@ -20,6 +20,14 @@ class Config:
|
|||
raise AttributeError(f'No such attribute: {name}')
|
||||
return __config
|
||||
|
||||
@staticmethod
|
||||
def read_password(password_field: str) -> str:
|
||||
if Path(password_field).is_file():
|
||||
with open(password_field, 'r') as f:
|
||||
return f.read()
|
||||
else:
|
||||
return password_field
|
||||
|
||||
|
||||
@classmethod
|
||||
def _locate_configuration_file(cls) -> Path | None:
|
||||
|
@ -56,7 +64,7 @@ class Config:
|
|||
hostname = db_config.get('hostname')
|
||||
port = db_config.get('port')
|
||||
username = db_config.get('username')
|
||||
password = db_config.get('password')
|
||||
password = cls.read_password(db_config.get('password'))
|
||||
database = db_config.get('database')
|
||||
return f"psycopg2+postgresql://{username}:{password}@{hostname}:{port}/{database}"
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue