Source code for schedula.utils.form.cli

# coding=utf-8
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2025, Vincenzo Arcidiacono;
# Licensed under the EUPL (the 'Licence');
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl

r"""
Define the command line interface for web forms.

.. click:: schedula.utils.form.cli:cli
   :prog: schedula form
   :show-nested:

"""
import os
import click
import logging
import click_log
import os.path as osp

log = logging.getLogger('schedula.utils.form.cli')


[docs] class _Logger(logging.Logger):
[docs] def setLevel(self, level): super(_Logger, self).setLevel(level) frmt = "%(asctime)-15s:%(levelname)5.5s:%(name)s:%(message)s" logging.basicConfig(level=level, format=frmt) rlog = logging.getLogger() # because `basicConfig()` does not reconfig root-logger when re-invoked. rlog.level = level logging.captureWarnings(True)
logger = _Logger('cli') click_log.basic_config(logger) @click.group( 'form', context_settings=dict(help_option_names=['-h', '--help']) ) def cli(): """ schedula forms command line tool. """ @cli.command( 'sample', short_help='Generates sample folder for a web form.' ) @click.option( '--folder', '-f', default='.', required=False, help="Main folder where app and src are contained.", type=click.Path(writable=True, file_okay=False) ) @click_log.simple_verbosity_option(logger) def sample(folder='.'): """ Writes a sample folder OUTPUT_FOLDER. OUTPUT_FOLDER: schedula WebForm folder template. [default: .] """ import glob import shutil base_dir = osp.dirname(__file__) sample_dir = osp.join(base_dir, 'sample') root_dir = osp.join(folder, 'root') for k in ('static', 'templates', 'translations'): shutil.copytree( osp.join(base_dir, k), osp.join(root_dir, k), dirs_exist_ok=True ) for fp in glob.glob(osp.join(sample_dir, '*'), include_hidden=True): if osp.relpath(fp, sample_dir) in ('package-lock.json', 'node_modules'): continue it = glob.glob( osp.join(fp, '**', '*.*'), include_hidden=True, recursive=True ) if osp.isdir(fp) else [fp] for f in it: dst = osp.join(folder, osp.relpath(f, sample_dir)) if osp.isfile(f) and not osp.isfile(dst): os.makedirs(osp.dirname(dst), exist_ok=True) shutil.copy(f, dst) @cli.command('build', short_help='Build main folder `src` files.') @click.option( '--folder', '-f', default='.', required=False, help="Main folder where app and src are contained.", type=click.Path(writable=True, file_okay=False) ) @click_log.simple_verbosity_option(logger) def build(folder='.'): """ Build main folder `src` files. """ import subprocess subprocess.run('npm i; npm run build', cwd=folder, shell=True) @cli.command( 'watch', short_help='Run server in debug with continuous build of main folder `src` ' 'files.', context_settings=dict( ignore_unknown_options=True, allow_extra_args=True, ) ) @click.option( '--folder', '-f', default='.', required=False, help="Main folder where app and src are contained.", type=click.Path(writable=True, file_okay=False) ) @click.option( '--app', '-a', help="The Flask application or factory function to load, in the form " "'module:name'.", default='app:app', required=False ) @click.option( '--only-flask', help="The Flask application or factory function to load, in the form " "'module:name'.", is_flag=True, required=False ) @click_log.simple_verbosity_option(logger) @click.pass_context def watch(ctx, folder='.', app='app:app', only_flask=False): """ Run server in debug with continuous build of main folder `src` files. MAIN_FOLDER: Folder path. [default: .] """ if only_flask: from livereload import Server from schedula.utils.form.gapp import get_module module, code = app.split(':') app = eval(code, get_module(module, (folder,))) app.debug = True options = { ctx.args[i][2:].replace('-', '_'): ctx.args[i + 1] for i in range(0, len(ctx.args), 2) } server = Server(app.wsgi_app) server.watch(osp.join(folder, 'root', '**', '*.*')) server.serve(**options) else: import subprocess subprocess.run( f'(npm i; npm run watch) & ' f'(python {__file__} watch --app {app} --only-flask ' f'{" ".join(ctx.args)})', cwd=folder, shell=True ) @cli.command('run', short_help='Run server.', context_settings=dict( ignore_unknown_options=True, allow_extra_args=True, )) @click.option( '--folder', '-f', default='.', required=False, help="Main folder where app and src are contained.", type=click.Path(writable=True, file_okay=False) ) @click.option( '--app', '-a', help="The Flask application or factory function to load, in the form " "'module:name'.", default='app:app', required=False ) @click_log.simple_verbosity_option(logger) @click.pass_context def run(ctx, folder='.', app='app:app'): """ Building of src files within MAIN_FOLDER. MAIN_FOLDER: Folder path. [default: .] """ from .gapp import Application, get_module module, code = app.split(':') options = { ctx.args[i][2:].replace('-', '_'): ctx.args[i + 1] for i in range(0, len(ctx.args), 2) } if 'SECRET_KEY' not in os.environ: import secrets os.environ['SECRET_KEY'] = secrets.token_hex(32) if 'SECURITY_PASSWORD_SALT' not in os.environ: import secrets salt = f'{secrets.SystemRandom().getrandbits(128)}' os.environ['SECURITY_PASSWORD_SALT'] = salt Application( app=eval(code, get_module(module, (folder,))), **options ).run() if __name__ == '__main__': cli()