ranchimallflo-api/py3.7/lib/python3.7/site-packages/quart/cli.py

237 lines
7.0 KiB
Python

import code
import os
import sys
from importlib import import_module
from pathlib import Path
from typing import Any, Callable, Iterable, List, Optional, TYPE_CHECKING
import click
try:
from dotenv import load_dotenv
except ImportError:
pass
from .__about__ import __version__
from .helpers import get_debug_flag
if TYPE_CHECKING:
from .app import Quart # noqa: F401
class NoAppException(click.UsageError):
def __init__(self) -> None:
super().__init__(
'Could not locate a Quart application as the QUART_APP environment '
'variable has either not been set or provided or does not point to '
'a valid application.\n'
'Please set it to module_name:app_name or module_name:factory_function()\n'
'Note `quart` is not a valid module_name.'
)
class ScriptInfo:
def __init__(
self,
app_import_path: Optional[str]=None,
create_app: Optional[Callable]=None,
) -> None:
self.load_dotenv_if_exists()
self.app_import_path = app_import_path or os.environ.get('QUART_APP')
self.create_app = create_app
self.data: dict = {}
self._app: Optional['Quart'] = None
def load_app(self) -> 'Quart':
if self._app is None:
if self.create_app is not None:
self._app = self.create_app()
else:
try:
module_name, app_name = self.app_import_path.split(':', 1)
except ValueError:
module_name, app_name = self.app_import_path, 'app'
except AttributeError:
raise NoAppException()
module_path = Path(module_name).resolve()
sys.path.insert(0, str(module_path.parent))
if module_path.is_file():
import_name = module_path.with_suffix('').name
else:
import_name = module_path.name
try:
module = import_module(import_name)
except ModuleNotFoundError as error:
if error.name == import_name:
raise NoAppException()
else:
raise
try:
self._app = eval(app_name, vars(module))
except NameError:
raise NoAppException()
from .app import Quart
if not isinstance(self._app, Quart):
self._app = None
raise NoAppException()
if self._app is None:
raise NoAppException()
self._app.debug = get_debug_flag()
return self._app
def load_dotenv_if_exists(self) -> None:
if os.environ.get('QUART_SKIP_DOTENV') == '1':
return
if not Path(".env").is_file() and not Path(".quartenv").is_file():
return
try:
if Path(".env").is_file():
load_dotenv()
if Path(".quartenv").is_file():
load_dotenv(dotenv_path=Path(".") / ".quartenv")
except NameError:
print( # noqa: T001
"* Tip: There are .env files present. "
"Do \"pip install python-dotenv\" to use them."
)
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
class AppGroup(click.Group):
def group(self, *args: Any, **kwargs: Any) -> Any:
kwargs.setdefault('cls', AppGroup)
return super().group(self, *args, **kwargs)
def get_version(ctx: Any, param: Any, value: Any) -> None:
if not value or ctx.resilient_parsing:
return
message = f"Quart {__version__}"
click.echo(message, color=ctx.color)
ctx.exit()
version_option = click.Option(
['--version'], help='Show the Quart version', expose_value=False,
callback=get_version, is_flag=True, is_eager=True,
)
class QuartGroup(AppGroup):
def __init__(
self,
add_default_commands: bool=True,
create_app: Optional[Callable]=None,
add_version_option: bool=True,
*,
params: Optional[List]=None,
**kwargs: Any,
) -> None:
params = params or []
if add_version_option:
params.append(version_option)
super().__init__(params=params, **kwargs)
self.create_app = create_app
if add_default_commands:
self.add_command(run_command)
self.add_command(shell_command)
def get_command(self, ctx: click.Context, name: str) -> click.Command:
"""Return the relevant command given the context and name.
.. warning::
This differs substaintially from Flask in that it allows
for the inbuilt commands to be overridden.
"""
info = ctx.ensure_object(ScriptInfo)
command = None
try:
command = info.load_app().cli.get_command(ctx, name)
except NoAppException:
pass
if command is None:
command = super().get_command(ctx, name)
return command
def list_commands(self, ctx: click.Context) -> Iterable[str]:
commands = set(click.Group.list_commands(self, ctx))
info = ctx.ensure_object(ScriptInfo)
commands.update(info.load_app().cli.list_commands(ctx))
return commands
def main(self, *args: Any, **kwargs: Any) -> Any:
kwargs.setdefault('obj', ScriptInfo(create_app=self.create_app))
return super().main(*args, **kwargs)
@click.command('run', short_help='Start and run a development server.')
@click.option('--host', '-h', default='127.0.0.1', help='The interface to serve on.')
@click.option('--port', '-p', default=5000, help='The port to serve on.')
@pass_script_info
def run_command(info: ScriptInfo, host: str, port: int) -> None:
app = info.load_app()
app.run(debug=True, host=host, port=port, use_reloader=True)
@click.command('shell', short_help='Open a shell within the app context.')
@pass_script_info
def shell_command(info: ScriptInfo) -> None:
app = info.load_app()
context = {}
context.update(app.make_shell_context())
banner = f"Python {sys.version} on {sys.platform} running {app.import_name}"
code.interact(banner=banner, local=context)
cli = QuartGroup(
help="""\
Utility functions for Quart applications.
This will load the app defined in the QUART_APP environment
variable. The QUART_APP variable follows the Gunicorn standard of
`module_name:application_name` e.g. `hello:app`.
\b
{prefix}{cmd} QUART_APP=hello:app
{prefix}{cmd} QUART_DEBUG=1
{prefix}quart run
""".format(
cmd='export' if os.name == 'posix' else 'set',
prefix='$ ' if os.name == 'posix' else '> ',
),
)
def main(as_module: bool=False) -> None:
args = sys.argv[1:]
if as_module:
name = 'python -m quart'
sys.argv = ['-m', 'quart'] + args
else:
name = None
cli.main(args=args, prog_name=name)
if __name__ == '__main__':
main(as_module=True)