feat: updated to newest flask-imp code.
This commit is contained in:
11
src/quart_imp/__init__.py
Normal file
11
src/quart_imp/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from .auth import Auth as Auth
|
||||
from .auth import PasswordGeneration as PasswordGeneration
|
||||
from .imp import Imp as Imp
|
||||
from .imp_blueprint import ImpBlueprint
|
||||
|
||||
__all__ = [
|
||||
"Auth",
|
||||
"PasswordGeneration",
|
||||
"Imp",
|
||||
"ImpBlueprint",
|
||||
]
|
||||
90
src/quart_imp/_cli/__init__.py
Normal file
90
src/quart_imp/_cli/__init__.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import click
|
||||
|
||||
from .blueprint import add_api_blueprint as _add_api_blueprint
|
||||
from .blueprint import add_blueprint as _add_blueprint
|
||||
from .helpers import Sprinkles as Sp
|
||||
from .init import init_app as _init_app
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli() -> None:
|
||||
pass # Entry Point
|
||||
|
||||
|
||||
@cli.command("blueprint", help="Create a quart-imp blueprint")
|
||||
@click.option(
|
||||
"-n",
|
||||
"--name",
|
||||
nargs=1,
|
||||
default="new_blueprint",
|
||||
prompt="Name",
|
||||
help="The name of the blueprint to create.",
|
||||
)
|
||||
@click.option(
|
||||
"-f",
|
||||
"--folder",
|
||||
nargs=1,
|
||||
default=".",
|
||||
prompt=(f"Folder {Sp.WARNING}(relative to CWD){Sp.END}"),
|
||||
help="The folder to create the blueprint in, creation is relative to the current working directory.",
|
||||
)
|
||||
def add_blueprint(name: str, folder: str) -> None:
|
||||
_add_blueprint(name=name, folder=folder)
|
||||
|
||||
|
||||
@cli.command("api-blueprint", help="Create a quart-imp api blueprint")
|
||||
@click.option(
|
||||
"-n",
|
||||
"--name",
|
||||
nargs=1,
|
||||
default="new_api_blueprint",
|
||||
prompt="Name",
|
||||
help="The name of the api blueprint to create.",
|
||||
)
|
||||
@click.option(
|
||||
"-f",
|
||||
"--folder",
|
||||
nargs=1,
|
||||
default=".",
|
||||
prompt=(f"Folder {Sp.WARNING}(relative to CWD){Sp.END}"),
|
||||
help="The folder to create the api blueprint in, creation is relative to the current working directory.",
|
||||
)
|
||||
def add_api_blueprint(name: str, folder: str) -> None:
|
||||
_add_api_blueprint(name=name, folder=folder)
|
||||
|
||||
|
||||
@cli.command("init", help="Create a new quart-imp app.")
|
||||
@click.option(
|
||||
"-n",
|
||||
"--name",
|
||||
nargs=1,
|
||||
default=None,
|
||||
help="The name of the app folder that will be created.",
|
||||
)
|
||||
@click.option("-s", "--slim", is_flag=True, default=False, help="Create a slim app.")
|
||||
@click.option(
|
||||
"-m", "--minimal", is_flag=True, default=False, help="Create a minimal app."
|
||||
)
|
||||
@click.option("-f", "--full", is_flag=True, default=False, help="Create a full app.")
|
||||
def init_new_app(name: str, full: bool, slim: bool, minimal: bool) -> None:
|
||||
if not full and not slim and not minimal:
|
||||
choice = click.prompt(
|
||||
"What type of app would you like to create?",
|
||||
default="minimal",
|
||||
type=click.Choice(["minimal", "slim", "full"]),
|
||||
)
|
||||
|
||||
if choice == "full":
|
||||
full = True
|
||||
elif choice == "slim":
|
||||
slim = True
|
||||
elif choice == "minimal":
|
||||
minimal = True
|
||||
|
||||
if name is None:
|
||||
set_name = click.prompt("What would you like to call your app?", default="app")
|
||||
|
||||
else:
|
||||
set_name = name
|
||||
|
||||
_init_app(set_name, full, slim, minimal)
|
||||
165
src/quart_imp/_cli/blueprint.py
Normal file
165
src/quart_imp/_cli/blueprint.py
Normal file
@@ -0,0 +1,165 @@
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from .filelib.head_tag_generator import head_tag_generator
|
||||
from .filelib.main_js import main_js
|
||||
from .filelib.quart_imp_logo import quart_imp_logo
|
||||
from .filelib.water_css import water_css
|
||||
from .helpers import Sprinkles as Sp
|
||||
from .helpers import build
|
||||
from .helpers import to_snake_case
|
||||
|
||||
|
||||
def add_api_blueprint(
|
||||
name: str = "new_api_blueprint",
|
||||
folder: str = ".",
|
||||
_init_app: bool = False,
|
||||
_cwd: t.Optional[Path] = None,
|
||||
_url_prefix: t.Optional[str] = None,
|
||||
) -> None:
|
||||
from .filelib.api_blueprint import api_blueprint_init_py
|
||||
from .filelib.api_blueprint import api_blueprint_routes_index_py
|
||||
|
||||
click.echo(f"{Sp.OKGREEN}Creating API Blueprint: {name}")
|
||||
|
||||
if _cwd:
|
||||
cwd = _cwd
|
||||
else:
|
||||
cwd = Path.cwd()
|
||||
|
||||
if not cwd.exists():
|
||||
click.echo(f"{Sp.FAIL}{folder} does not exist.{Sp.END}")
|
||||
return
|
||||
|
||||
name = to_snake_case(name)
|
||||
|
||||
if folder == ".":
|
||||
root_folder = cwd / name
|
||||
else:
|
||||
root_folder = cwd / folder / name
|
||||
|
||||
folders: t.Dict[str, Path] = {
|
||||
"root": root_folder,
|
||||
"routes": root_folder / "routes",
|
||||
}
|
||||
|
||||
files: t.Dict[str, t.Tuple[Path, t.Any]] = {
|
||||
"root/__init__.py": (
|
||||
folders["root"] / "__init__.py",
|
||||
api_blueprint_init_py(
|
||||
url_prefix=name if not _url_prefix else _url_prefix, name=name
|
||||
),
|
||||
),
|
||||
"routes/index.py": (
|
||||
folders["routes"] / "index.py",
|
||||
api_blueprint_routes_index_py(),
|
||||
),
|
||||
}
|
||||
|
||||
build(folders, files, building="API Blueprint")
|
||||
|
||||
|
||||
def add_blueprint(
|
||||
name: str = "new_blueprint",
|
||||
folder: str = ".",
|
||||
_init_app: bool = False,
|
||||
_cwd: t.Optional[Path] = None,
|
||||
_url_prefix: t.Optional[str] = None,
|
||||
) -> None:
|
||||
from .filelib.blueprint import blueprint_init_py
|
||||
from .filelib.blueprint import blueprint_routes_index_py
|
||||
from .filelib.blueprint import blueprint_templates_index_html
|
||||
from .filelib.blueprint import blueprint_init_app_templates_index_html
|
||||
from .filelib.blueprint import blueprint_templates_extends_main_html
|
||||
from .filelib.blueprint import blueprint_templates_includes_header_html
|
||||
from .filelib.blueprint import blueprint_templates_includes_footer_html
|
||||
|
||||
click.echo(f"{Sp.OKGREEN}Creating Blueprint: {name}")
|
||||
|
||||
if _cwd:
|
||||
cwd = _cwd
|
||||
else:
|
||||
cwd = Path.cwd()
|
||||
|
||||
if not cwd.exists():
|
||||
click.echo(f"{Sp.FAIL}{folder} does not exist.{Sp.END}")
|
||||
return
|
||||
|
||||
name = to_snake_case(name)
|
||||
|
||||
if folder == ".":
|
||||
root_folder = cwd / name
|
||||
else:
|
||||
root_folder = cwd / folder / name
|
||||
|
||||
folders: t.Dict[str, Path] = {
|
||||
"root": root_folder,
|
||||
"routes": root_folder / "routes",
|
||||
"static": root_folder / "static",
|
||||
"static/img": root_folder / "static" / "img",
|
||||
"static/css": root_folder / "static" / "css",
|
||||
"static/js": root_folder / "static" / "js",
|
||||
"templates": root_folder / "templates" / name,
|
||||
"templates/extends": root_folder / "templates" / name / "extends",
|
||||
"templates/includes": root_folder / "templates" / name / "includes",
|
||||
}
|
||||
|
||||
files: t.Dict[str, t.Tuple[Path, t.Any]] = {
|
||||
"root/__init__.py": (
|
||||
folders["root"] / "__init__.py",
|
||||
blueprint_init_py(
|
||||
url_prefix=name if not _url_prefix else _url_prefix, name=name
|
||||
),
|
||||
),
|
||||
"routes/index.py": (
|
||||
folders["routes"] / "index.py",
|
||||
blueprint_routes_index_py(),
|
||||
),
|
||||
"static/img/quart-imp-logo.png": (
|
||||
folders["static/img"] / "quart-imp-logo.png",
|
||||
quart_imp_logo,
|
||||
),
|
||||
"static/water.css": (folders["static/css"] / "water.css", water_css),
|
||||
"static/main.js": (
|
||||
folders["static/js"] / "main.js",
|
||||
main_js(main_js_=folders["static"] / "main.js"),
|
||||
),
|
||||
"templates/-/index.html": (
|
||||
folders["templates"] / "index.html",
|
||||
blueprint_templates_index_html(root=folders["root"], blueprint_name=name)
|
||||
if not _init_app
|
||||
else blueprint_init_app_templates_index_html(
|
||||
blueprint_name=name,
|
||||
index_html=folders["templates"] / "index.html",
|
||||
extends_main_html=folders["templates/extends"] / "main.html",
|
||||
index_py=folders["routes"] / "index.py",
|
||||
init_py=folders["root"] / "__init__.py",
|
||||
),
|
||||
),
|
||||
"templates/-/extends/main.html": (
|
||||
folders["templates/extends"] / "main.html",
|
||||
blueprint_templates_extends_main_html(
|
||||
name=name,
|
||||
head_tag=head_tag_generator(f"{name}.static"),
|
||||
),
|
||||
),
|
||||
"templates/-/includes/header.html": (
|
||||
folders["templates/includes"] / "header.html",
|
||||
blueprint_templates_includes_header_html(
|
||||
header_html=folders["templates/includes"] / "header.html",
|
||||
main_html=folders["templates/extends"] / "main.html",
|
||||
static_url_endpoint=f"{name}.static",
|
||||
),
|
||||
),
|
||||
"templates/-/includes/footer.html": (
|
||||
folders["templates/includes"] / "footer.html",
|
||||
blueprint_templates_includes_footer_html(
|
||||
footer_html=folders["templates/includes"] / "footer.html",
|
||||
main_html=folders["templates/extends"] / "main.html",
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
build(folders, files, building="Blueprint")
|
||||
0
src/quart_imp/_cli/filelib/__init__.py
Normal file
0
src/quart_imp/_cli/filelib/__init__.py
Normal file
24
src/quart_imp/_cli/filelib/api_blueprint.py
Normal file
24
src/quart_imp/_cli/filelib/api_blueprint.py
Normal file
@@ -0,0 +1,24 @@
|
||||
def api_blueprint_init_py(url_prefix: str, name: str) -> str:
|
||||
return f"""\
|
||||
from quart_imp import ImpBlueprint
|
||||
from quart_imp.config import ImpBlueprintConfig
|
||||
|
||||
bp = ImpBlueprint(__name__, ImpBlueprintConfig(
|
||||
enabled=True,
|
||||
url_prefix="/{url_prefix}",
|
||||
init_session={{"{name}_session_loaded": True}},
|
||||
))
|
||||
|
||||
bp.import_resources("routes")
|
||||
"""
|
||||
|
||||
|
||||
def api_blueprint_routes_index_py() -> str:
|
||||
return """\
|
||||
from .. import bp
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
async def index():
|
||||
return await {"message": "Hello, World!"}
|
||||
"""
|
||||
123
src/quart_imp/_cli/filelib/blueprint.py
Normal file
123
src/quart_imp/_cli/filelib/blueprint.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def blueprint_init_py(url_prefix: str, name: str) -> str:
|
||||
return f"""\
|
||||
from quart_imp import ImpBlueprint
|
||||
from quart_imp.config import ImpBlueprintConfig
|
||||
|
||||
bp = ImpBlueprint(__name__, ImpBlueprintConfig(
|
||||
enabled=True,
|
||||
url_prefix="/{url_prefix}",
|
||||
static_folder="static",
|
||||
template_folder="templates",
|
||||
init_session={{"{name}_session_loaded": True}},
|
||||
))
|
||||
|
||||
bp.import_resources("routes")
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_routes_index_py() -> str:
|
||||
return """\
|
||||
from quart import render_template
|
||||
|
||||
from .. import bp
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
async def index():
|
||||
return await render_template(bp.tmpl("index.html"))
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_templates_index_html(blueprint_name: str, root: Path) -> str:
|
||||
return f"""\
|
||||
{{% extends '{blueprint_name}/extends/main.html' %}}
|
||||
|
||||
{{% block content %}}
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 2rem; margin-bottom: 2rem;">
|
||||
<div>
|
||||
<h2 style="margin: 0;">Blueprint: {blueprint_name}</h2>
|
||||
<h3>Here's your new blueprint.</h3>
|
||||
<p>Located here: <code>{root}</code></p>
|
||||
<p style="margin-bottom: 0;">Remember to double-check the config.toml file.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{% endblock %}}
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_init_app_templates_index_html(
|
||||
blueprint_name: str,
|
||||
index_html: Path,
|
||||
extends_main_html: Path,
|
||||
index_py: Path,
|
||||
init_py: Path,
|
||||
) -> str:
|
||||
return f"""\
|
||||
{{% extends 'www/extends/main.html' %}}
|
||||
|
||||
{{% block content %}}
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 2rem; margin-bottom: 2rem;">
|
||||
<div>
|
||||
<h2 style="margin: 0;">Blueprint: {blueprint_name}</h2>
|
||||
<h3>This is the index route of the included example blueprint.</h3>
|
||||
<p style="margin-bottom: 0;">
|
||||
This template page is located in <code>{index_html}</code><br/>
|
||||
it extends from <code>{extends_main_html}</code><br/>
|
||||
with its route defined in <code>{index_py}</code><br/><br/>
|
||||
It's being imported by <code>bp.import_resources("routes")</code>
|
||||
in the <code>{init_py}</code> file.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{% endblock %}}
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_templates_extends_main_html(name: str, head_tag: str) -> str:
|
||||
return f"""\
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
{head_tag}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{% include '{name}/includes/header.html' %}}
|
||||
{{% block content %}}{{% endblock %}}
|
||||
{{% include '{name}/includes/footer.html' %}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_templates_includes_header_html(
|
||||
header_html: Path, main_html: Path, static_url_endpoint: str
|
||||
) -> str:
|
||||
return f"""\
|
||||
<div style="display: flex; flex-direction: row; align-items: center;
|
||||
justify-content: start; gap: 2rem; margin-bottom: 2rem;">
|
||||
<img style="border-radius: 50%"
|
||||
src="{{{{ url_for('{static_url_endpoint}', filename='img/quart-imp-logo.png') }}}}" alt="quart-imp logo">
|
||||
<h1 style="font-size: 4rem;">Quart-Imp</h1>
|
||||
</div>
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<p>This is the header, located here: <code>{header_html}</code></p>
|
||||
<p>It's being imported in the <code>{main_html}</code> template.</p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def blueprint_templates_includes_footer_html(footer_html: Path, main_html: Path) -> str:
|
||||
return f"""\
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 2rem; margin-bottom: 2rem;">
|
||||
<div>
|
||||
<p>This is the footer, located here: <code>{footer_html}</code></p>
|
||||
<p>It's being imported in the <code>{main_html}</code> template.</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
6
src/quart_imp/_cli/filelib/extensions.py
Normal file
6
src/quart_imp/_cli/filelib/extensions.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def extensions_init() -> str:
|
||||
return """\
|
||||
from quart_imp import Imp
|
||||
|
||||
imp = Imp()
|
||||
"""
|
||||
505
src/quart_imp/_cli/filelib/favicon.py
Normal file
505
src/quart_imp/_cli/filelib/favicon.py
Normal file
@@ -0,0 +1,505 @@
|
||||
favicon = (
|
||||
"0000010003003030000001002000a8250000360000002020000001002000"
|
||||
"a8100000de25000010100000010020006804000086360000280000003000"
|
||||
"000060000000010020000000000000240000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000039393902393939113939392d3939394c393939773939"
|
||||
"399b393939b2393939bd393939bb393939ae393939943939396c39393943"
|
||||
"393939263939390c39393901000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000003939390e3939393a39393977393939bc3939"
|
||||
"39e1393939f2393939fa393939fd393939ff393939ff393939ff393939ff"
|
||||
"393939fd393939f8393939ee393939db393939ad393939673939392d3939"
|
||||
"390700000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000393939123939395c393939b03939"
|
||||
"39ec393939fe393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939fc393939e13939399b39393947393939080000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000393939063939394c3939"
|
||||
"39b7393939f6393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ed"
|
||||
"393939a03939393439393902000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000003939"
|
||||
"39183939398d393939ec393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939dd3939396c3939390a0000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000039393933393939b4393939fa393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939f2393939933939391d00000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000039393941393939cf393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939fd393939b339393926"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000039393947393939d9"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939fe393939be393939290000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"39393939393939d6393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff38383bff3838"
|
||||
"3bff393939ff393938ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393938ff393938ff393939ff393939ff393939ff393939b73939"
|
||||
"391e00000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000039393921393939bb393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3737"
|
||||
"3fff33334dff2e2f5aff2e2e5bff313152ff363643ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff39393aff323357ff373744ff393938ff3939"
|
||||
"39ff393939ff393939fe3939399239393909000000000000000000000000"
|
||||
"000000000000000000000000000000003939390b3939399b393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff363643ff32324fff353545ff37373fff37373eff353546ff"
|
||||
"2e2f5aff353545ff393938ff393939ff393939ff393939ff38383eff2326"
|
||||
"91ff252889ff373742ff393938ff393939ff393939ff393939f33939396c"
|
||||
"393939020000000000000000000000000000000000000000000000003939"
|
||||
"395c393939f2393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff37373fff32334dff38383cff393938ff"
|
||||
"393939ff393939ff393938ff353644ff313153ff39393aff393939ff3939"
|
||||
"39ff393938ff363646ff1f23a0ff171dbcff262985ff373742ff393938ff"
|
||||
"393939ff393939ff393939dd393939340000000000000000000000000000"
|
||||
"0000000000003939391f393939c8393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff313250ff"
|
||||
"363643ff393938ff393939ff393939ff393939ff393939ff393939ff3131"
|
||||
"51ff363641ff393939ff393939ff393938ff34354eff1c21abff171dbdff"
|
||||
"181ebaff282b7eff38383dff393939ff393939ff393939ff3939399f3939"
|
||||
"39080000000000000000000000000000000039393970393939fc393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff38383bff2f2f58ff37373eff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393938ff33334bff353545ff393938ff393939ff393937ff"
|
||||
"323356ff191fb4ff171dbcff171dbdff191fb5ff2d2f6aff393939ff3939"
|
||||
"39ff393939ff393939ed3939394800000000000000000000000039393919"
|
||||
"393939cb393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff38383eff313152ff38383bff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393938ff353545ff343548ff"
|
||||
"393938ff393939ff393937ff2f3160ff181eb8ff171dbcff171dbcff171d"
|
||||
"bdff1d22a8ff34354fff393938ff393939ff393939ff3939399a39393906"
|
||||
"000000000000000039393950393939f5393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393938ff343448ff28296cff2a2b65ff363642ff3334"
|
||||
"4bff353547ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393938ff363642ff33334bff393938ff393939ff3a3937ff2d2f6aff181d"
|
||||
"bbff171dbcff171dbcff171dbcff171dbcff252888ff38383dff393939ff"
|
||||
"393939ff393939df3939392b0000000039393906393939a0393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff373740ff2628"
|
||||
"71ff26286fff2e2f5aff343449ff39393aff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff363642ff33344aff393938ff3939"
|
||||
"39ff393938ff292c77ff171dbdff171dbcff171dbcff171dbcff171dbdff"
|
||||
"1a1fb2ff30315eff393938ff393939ff393939fc39393964000000003939"
|
||||
"391f393939d3393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff363642ff38383eff39393aff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393938ff3233"
|
||||
"4eff343448ff393938ff393939ff38383cff252889ff171dbeff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbeff23278eff38383eff393939ff3939"
|
||||
"39ff393939ad3939390c39393951393939ef393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393938ff373740ff2f3057ff37373eff393939ff393938ff373743ff"
|
||||
"1f249eff171dbdff171dbcff171dbcff171dbcff171dbcff171dbdff1a1f"
|
||||
"b1ff31325bff393938ff393939ff393939db3939392639393988393939fc"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff38383bff2f3057ff32334cff393938ff"
|
||||
"393939ff393938ff34354dff1b20adff171dbdff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbdff272983ff39393aff393939ff393939ee"
|
||||
"39393943393939b6393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff38383cff2e2f58ff"
|
||||
"303154ff39393aff393939ff393939ff393937ff2f3060ff191eb7ff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbeff1f23a0ff"
|
||||
"373742ff393938ff393939f83939396c393939d9393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393938ff393938ff3939"
|
||||
"38ff393939ff393939ff393939ff393939ff393939ff393939ff393938ff"
|
||||
"373740ff2f2f59ff313250ff39393aff393939ff393939ff393938ff3434"
|
||||
"47ff212490ff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbdff1b20afff333451ff393938ff393939fd393939943939"
|
||||
"39f0393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393938ff3e3e"
|
||||
"3cff393a42ff323248ff363642ff38383cff393939ff393939ff393939ff"
|
||||
"393939ff393939ff353546ff2e2f59ff34344aff393939ff393939ff3939"
|
||||
"38ff39393aff313152ff22247fff191eafff171dbdff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff191eb7ff2e3065ff3939"
|
||||
"37ff393939ff393939ae393939fc393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3838"
|
||||
"38ff41413eff616059ff9e9c90ff9e9fafff4e4f7eff2c2d66ff33334bff"
|
||||
"393939ff393938ff393939ff37373fff313250ff303154ff373740ff3939"
|
||||
"38ff393938ff393939ff363642ff2b2c63ff1f2186ff1b1e95ff181db7ff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff2b2d72ff393937ff393939ff393939bb393939fe393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff383838ff565553ffa5a498ff707090ff9695a6ffbabad2ff"
|
||||
"9b9bb2ff6d6d72ff39393cff38383bff363642ff32334eff303153ff3535"
|
||||
"46ff39393aff393939ff37373fff33344aff2b2c62ff22247dff1c1f8cff"
|
||||
"1c1f8dff1a1ea1ff171dbbff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbdff292c77ff3a3937ff393939ff"
|
||||
"393939bd393939f4393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff373737ff646462ffe2e2deff"
|
||||
"b8b7bcff3f4088ff1a1f90ff313373ff545469ff353553ff2e2f58ff2e2f"
|
||||
"5aff32324fff343449ff33334cff2f3056ff2a2b65ff232578ff1e2186ff"
|
||||
"1c1f8dff1c1f8eff1c1f8dff1c1f91ff181eb0ff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"2a2c74ff393937ff393939ff393939b2393939e0393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff3a3a3aff858489ff9494a7ff232689ff0d1190ff14188aff1f21"
|
||||
"82ff22247dff232579ff232579ff21247eff1f2284ff1e2088ff1d1f8bff"
|
||||
"1c1f8eff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8cff1b1e9cff171d"
|
||||
"baff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff181eb9ff2d2f6aff393937ff393939fe3939399b3939"
|
||||
"39c0393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff37373dff2c2d71ff272982ff1115"
|
||||
"8aff0d1290ff14188fff1c1f8dff1c1f8eff1c1f8eff1c1f8eff1c1f8eff"
|
||||
"1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8fff191eadff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbdff1a1fb2ff323357ff3939"
|
||||
"38ff393939f93939397739393995393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393938ff3232"
|
||||
"4fff1d248aff192292ff191c8cff1d2077ff1d2086ff1c1f8dff1c1f8dff"
|
||||
"1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1a1e9cff171dbaff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bdff1d22a5ff363646ff393938ff393939f23939394c39393960393939f3"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff3a3937ff2f3055ff1c238eff1b2491ff1d1f8bff2e2f59ff"
|
||||
"242678ff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f91ff181eafff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbeff23278fff38383cff393939ff393939e1"
|
||||
"3939392d39393929393939db393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff383a3bff363d41ff343848ff1b368fff"
|
||||
"1541a8ff232a7cff353546ff28296cff1c1f8eff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff"
|
||||
"1c1f8dff1a1ea3ff171dbbff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff191eb6ff2e3065ff"
|
||||
"393938ff393939ff393939bb393939113939390b393939b0393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff363c40ff"
|
||||
"25506bff20587cff146097ff1075b9ff2d4861ff393838ff292b68ff1c1f"
|
||||
"8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff"
|
||||
"1c1f8dff1c1f8dff1c1f8dff1b1f9aff181db7ff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbeff212598ff373742ff393938ff393939fe39393976000000000000"
|
||||
"000039393963393939f9393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff353d42ff2e4b5eff265a7bff226288ff363f"
|
||||
"44ff39383aff282a6bff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff"
|
||||
"1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1b1e97ff181db3ff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff191eb7ff2d2f69ff393938ff393939ff3939"
|
||||
"39ea39393937000000000000000039393925393939db393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393838ff3640"
|
||||
"46ff2f4c5eff373d41ff393938ff38383dff252675ff1c1f8eff1c1f8dff"
|
||||
"1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1b1f"
|
||||
"94ff181db1ff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbdff222596ff3737"
|
||||
"41ff393938ff393939ff393939af3939390e000000000000000039393903"
|
||||
"39393987393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393938ff3a3837ff393939ff393938ff363642ff"
|
||||
"212380ff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1b1f95ff181eafff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bdff1b20afff31325aff393938ff393939ff393939f53939395b00000000"
|
||||
"0000000000000000000000003939392f393939db393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393938ff32334eff1e2188ff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1b1f9aff181db2ff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff181eb8ff2b2d74ff39393aff393939ff393939ff"
|
||||
"393939b73939391200000000000000000000000000000000393939013939"
|
||||
"3977393939fb393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff2a2b65ff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f91ff1a1ea3ff181db7ff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbbff262985ff38383fff"
|
||||
"393938ff393939ff393939eb3939394c0000000000000000000000000000"
|
||||
"0000000000000000000039393917393939b7393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3737"
|
||||
"41ff22247cff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8fff1b1e9bff"
|
||||
"181eb0ff171dbbff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbdff181eb8ff"
|
||||
"262985ff373744ff393938ff393939ff393939fb3939398d393939060000"
|
||||
"00000000000000000000000000000000000000000000000000003939393b"
|
||||
"393939d9393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393938ff2f3057ff1d208aff1c1f8dff1c1f8dff1c1f8eff"
|
||||
"1b1f98ff191eabff171db9ff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbdff181eb7ff282a7eff373840ff393938ff393939ff393939ff3939"
|
||||
"39b439393918000000000000000000000000000000000000000000000000"
|
||||
"00000000000000003939390139393959393939ea393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393938ff38383cff262771ff1c1f8fff"
|
||||
"1b1f93ff1a1e9eff191eadff181db8ff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbdff171dbdff1c21abff2c2e6fff38383fff393938ff3939"
|
||||
"39ff393939ff393939d23939393400000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000393939053939"
|
||||
"396b393939ee393939ff393939ff393939ff393939ff39393aff363646ff"
|
||||
"2f3060ff21248fff1a1eaaff181db5ff171dbbff171dbdff171dbcff171d"
|
||||
"bcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbdff171dbdff1a1fb2ff24278cff333453ff3939"
|
||||
"39ff393938ff393939ff393939ff393939da393939450000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000003939390639393968393939e8393939ff393939ff"
|
||||
"393939ff39393aff2e3067ff1f23a0ff181eb8ff171dbeff171dbeff171d"
|
||||
"bdff171dbdff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbdff171dbdff171dbeff181dbaff1d21a9ff262986ff3132"
|
||||
"5bff38383dff393938ff393939ff393939ff393939ff393939d239393944"
|
||||
"393939010000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000039393904"
|
||||
"39393957393939d9393939ff393939ff393939ff39393bff34354eff2d2f"
|
||||
"69ff262984ff20249dff1b20afff191fb5ff181eb9ff171dbcff171dbeff"
|
||||
"171dbeff171dbdff181ebaff191eb6ff1b20afff20249eff262985ff2d2f"
|
||||
"6aff34354eff39393aff393938ff393939ff393939ff393939ff393939fd"
|
||||
"393939bb3939393700000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000393939023939393a393939b7393939fa3939"
|
||||
"39ff393939ff393938ff393937ff39393aff373743ff333450ff303160ff"
|
||||
"2c2e6dff2a2c77ff282b7cff282b7cff292c78ff2c2e70ff2f3162ff3334"
|
||||
"51ff373744ff39393bff393937ff393938ff393939ff393939ff393939ff"
|
||||
"393939ff393939f13939399b393939210000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"00003939391639393973393939da393939fe393939ff393939ff393939ff"
|
||||
"393938ff393938ff393938ff393937ff393937ff393937ff393937ff3939"
|
||||
"37ff393937ff393938ff393938ff393938ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939fc393939c6393939583939390a000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000393939023939392d39393987"
|
||||
"393939db393939fa393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939f5393939cb3939396f3939391e0000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000393939053939392439393963393939b0393939dc3939"
|
||||
"39f3393939fe393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939fc393939ee393939d4393939a0393939503939"
|
||||
"391939393902000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"00003939390a393939293939396039393995393939bf393939df393939f4"
|
||||
"393939fe393939fc393939f0393939d9393939b639393988393939503939"
|
||||
"391f39393906000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000fffff81fffff0000ffff8001ffff0000fffc0000"
|
||||
"3fff0000fff000000fff0000ffc0000007ff0000ff80000001ff0000ff00"
|
||||
"000000ff0000fe000000007f0000fc000000003f0000f8000000001f0000"
|
||||
"f0000000001f0000f0000000000f0000e000000000070000e00000000007"
|
||||
"0000c000000000030000c000000000030000800000000003000080000000"
|
||||
"000100008000000000010000000000000001000000000000000100000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000010000000000000001"
|
||||
"0000800000000001000080000000000100008000000000030000c0000000"
|
||||
"00030000c000000000030000c000000000070000e000000000070000f000"
|
||||
"0000000f0000f0000000000f0000f8000000001f0000fc000000003f0000"
|
||||
"fe000000007f0000ff00000000ff0000ff80000001ff0000ffc0000003ff"
|
||||
"0000fff000000fff0000fff800003fff0000ffff0000ffff0000ffffe007"
|
||||
"ffff00002800000020000000400000000100200000000000001000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000393939103939"
|
||||
"394139393975393939a0393939c4393939d4393939d3393939bf39393999"
|
||||
"3939396e393939383939390b000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000003939391f3939"
|
||||
"3970393939be393939ec393939fd393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939fc393939e6393939b339393960393939160000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000003939390d3939"
|
||||
"3966393939d3393939fd393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39fb393939c5393939533939390700000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000003939"
|
||||
"391f3939399d393939f8393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939f1393939853939391300000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000039393929393939bc393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939fc"
|
||||
"393939a63939391a00000000000000000000000000000000000000000000"
|
||||
"00000000000039393922393939c0393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff38383cff38383dff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393938ff393939ff393939fe393939a83939391400000000000000000000"
|
||||
"0000000000000000000039393910393939a5393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff38383bff353546ff32324eff32324fff33334bff38383cff393939ff"
|
||||
"393939ff38383dff2f3162ff373741ff393938ff393939fc393939853939"
|
||||
"39060000000000000000000000000000000039393972393939fa393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff39393aff353546ff363741ff39393aff39393aff343547ff"
|
||||
"343448ff393939ff393938ff363745ff1f23a1ff262983ff38383fff3939"
|
||||
"38ff393939f13939395400000000000000000000000039393929393939db"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393938ff363642ff343449ff393938ff393939ff"
|
||||
"393939ff39393aff33334bff38393bff393938ff34354dff1b20aeff171d"
|
||||
"bbff292b7aff39393bff393939ff393939c4393939170000000000000000"
|
||||
"39393982393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff39393aff38383bff393939ff353545ff353644ff"
|
||||
"393938ff393939ff393939ff393939ff343547ff38383dff393937ff3233"
|
||||
"56ff191fb5ff171dbdff191fb4ff2f3160ff393938ff393939fb39393960"
|
||||
"000000003939391d393939cd393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393938ff363643ff282a6bff303154ff"
|
||||
"343448ff38383cff393939ff393939ff393939ff393939ff353546ff3737"
|
||||
"3eff3a3a37ff2f315fff181eb9ff171dbcff171dbeff20249cff363744ff"
|
||||
"393938ff393939b13939390a39393956393939f5393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff38383bff"
|
||||
"32324eff343547ff38383cff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff33344aff38383dff3a3937ff2b2e6fff171dbcff171dbcff171dbcff"
|
||||
"181dbaff2c2e6eff393938ff393939e53939393839393995393939ff3939"
|
||||
"39ff393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393938ff393938ff393939ff393939ff393939ff3939"
|
||||
"39ff393938ff363643ff33334cff393939ff393939ff262986ff171dbeff"
|
||||
"171dbcff171dbcff171dbeff1f249eff373743ff393938fc3939396e3939"
|
||||
"39cb393939ff393939ff393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393938ff363642ff303152ff37373eff393938ff38383fff"
|
||||
"20249bff171dbeff171dbcff171dbcff171dbcff191eb6ff31325aff3939"
|
||||
"37ff39393999393939eb393939ff393939ff393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff383838ff373737ff36363aff38383aff3939"
|
||||
"39ff393939ff393939ff393939ff353545ff313251ff37373fff393938ff"
|
||||
"38383cff2a2c69ff191eb3ff171dbdff171dbcff171dbcff171dbcff171d"
|
||||
"bcff2b2d72ff393938ff393939bf393939fb393939ff393939ff393939ff"
|
||||
"393939ff393939ff393939ff393939ff3b3b3aff504f4cff81807eff5c5c"
|
||||
"7cff333456ff38383bff393939ff37383eff33344aff33344aff38383bff"
|
||||
"393939ff343547ff27286eff1b1f9bff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbeff262985ff393939ff393939d3393939fd393939ff"
|
||||
"393939ff393939ff393939ff393939ff393939ff383838ff797873ff9b9a"
|
||||
"a4ff8181a8ff7b7ca5ff565662ff353542ff333449ff33334cff353545ff"
|
||||
"353644ff303153ff282a6aff202282ff1c1f8fff191eabff171dbdff171d"
|
||||
"bcff171dbcff171dbcff171dbcff161dbeff24288bff393939ff393939d4"
|
||||
"393939ef393939ff393939ff393939ff393939ff393939ff393939ff3838"
|
||||
"37ff626262ff9d9daeff24288cff151986ff2b2d77ff262870ff262870ff"
|
||||
"262871ff242678ff202381ff1d208aff1c1f8eff1c1f8dff1b1f97ff171d"
|
||||
"b8ff171dbcff171dbcff171dbcff171dbcff171dbcff171dbeff262983ff"
|
||||
"393939ff393939c3393939d2393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393939ff393938ff313252ff272a86ff14188aff15198cff1b1e8eff"
|
||||
"1c1f8dff1c1f8eff1c1f8eff1c1f8eff1c1f8eff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff191ea8ff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff2c2e6fff393937ff393939a0393939a0393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393939ff3a3937ff2a2c63ff192394ff20237eff"
|
||||
"252772ff1c1f8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8cff1b1f97ff181db7ff171dbcff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbdff191fb4ff313357ff393937fd39393975393939603939"
|
||||
"39f8393939ff393939ff393939ff393939ff393939ff334149ff26436cff"
|
||||
"144ba5ff2a3767ff2d2d5dff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f90ff191eadff171dbcff171dbcff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbeff212597ff373840ff393938eb3939"
|
||||
"394139393925393939d6393939ff393939ff393939ff393939ff393939ff"
|
||||
"32414aff265471ff20628bff344249ff2d2e5bff1c1f8dff1c1f8dff1c1f"
|
||||
"8dff1c1f8dff1c1f8dff1c1f8dff1c1f8eff1a1ea6ff171dbbff171dbcff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff181eb8ff2e3065ff3939"
|
||||
"37ff393939bc3939390f3939390239393991393939ff393939ff393939ff"
|
||||
"393939ff393939ff393938ff364047ff354047ff393938ff2a2b66ff1c1f"
|
||||
"8eff1c1f8dff1c1f8dff1c1f8dff1c1f8dff1c1f8eff1a1ea2ff171dbaff"
|
||||
"171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171dbeff2226"
|
||||
"93ff373840ff393939fe3939396f000000000000000039393936393939e5"
|
||||
"393939ff393939ff393939ff393939ff393939ff393938ff393938ff3838"
|
||||
"3cff252774ff1c1f8eff1c1f8dff1c1f8dff1c1f8cff1c1f90ff1a1ea5ff"
|
||||
"171dbaff171dbcff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bdff1c21abff323354ff393938ff393939d2393939200000000000000000"
|
||||
"3939390339393986393939fe393939ff393939ff393939ff393939ff3939"
|
||||
"39ff393938ff343448ff1f2285ff1c1f8dff1c1f8dff1c1f8dff1b1f98ff"
|
||||
"191eaeff171dbbff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbdff1a20b0ff2e3065ff393938ff393939f83939396700000000"
|
||||
"0000000000000000000000003939391a393939bc393939ff393939ff3939"
|
||||
"39ff393939ff393939ff393938ff2b2d61ff1c1f8cff1c1f8dff1b1f94ff"
|
||||
"191ea6ff181db7ff171dbdff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbeff1b20acff2f3063ff393939ff393939ff3939399d"
|
||||
"3939390d0000000000000000000000000000000000000000393939343939"
|
||||
"39d4393939ff393939ff393939ff393939ff363643ff23257fff1a1e9dff"
|
||||
"191ea9ff181db6ff171dbcff171dbcff171dbcff171dbcff171dbcff171d"
|
||||
"bcff171dbcff171dbeff181dbaff212599ff313258ff393939ff393939ff"
|
||||
"393939be3939392000000000000000000000000000000000000000000000"
|
||||
"0000000000003939393e393939d2393939ff393938ff373743ff282b7dff"
|
||||
"1b20adff171dbcff161dbfff171dbeff171dbdff171dbdff171dbdff171d"
|
||||
"beff171dbeff171dbdff191fb5ff20249aff2c2e6dff373743ff393938ff"
|
||||
"393939ff393939be3939392a000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000039393932393939bd393939fd"
|
||||
"39393aff353649ff2f3062ff282b7dff222694ff1e23a2ff1c21aaff1b20"
|
||||
"adff1c21a9ff1f23a0ff23278fff2a2d74ff313259ff373740ff393938ff"
|
||||
"393938ff393939fa393939a5393939210000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"3939391939393984393939e6393938ff393937ff393938ff38383dff3737"
|
||||
"44ff353648ff353649ff353648ff373742ff39393bff393938ff393937ff"
|
||||
"393939ff393939fe393939db393939703939391000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000393939033939393539393992393939d73939"
|
||||
"39f8393939ff393938ff393938ff393938ff393938ff393938ff393939ff"
|
||||
"393939ff393939f5393939ce393939823939392839393901000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000003939"
|
||||
"39033939392539393960393939a0393939d1393939ef393939fd393939fb"
|
||||
"393939eb393939cb39393995393939563939391c39393901000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"fff81fffffc003ffff0000fffc00003ff800001ff000000fe0000007e000"
|
||||
"0007c0000003800000038000000180000001000000010000000000000000"
|
||||
"000000000000000000000000000000000000000180000001800000018000"
|
||||
"0003c0000003c0000007e0000007f000000ff800001ffc00003ffe0000ff"
|
||||
"ff8001fffff00fff28000000100000002000000001002000000000000004"
|
||||
"000000000000000000000000000000000000000000000000000000000000"
|
||||
"39393904393939373939398e393939cb393939e8393939e6393939c83939"
|
||||
"398739393931393939020000000000000000000000000000000000000000"
|
||||
"393939103939397f393939e5393939fe393939ff393939ff393939ff3939"
|
||||
"39ff393939fe393939e1393939743939390c000000000000000000000000"
|
||||
"39393912393939a0393939fc393939ff393939ff393939ff393939ff3838"
|
||||
"3bff38383cff393939ff393939ff393939fa393938933939390c00000000"
|
||||
"3939390339393985393939fd393939ff393939ff393939ff393939ff3838"
|
||||
"3dff363642ff353544ff38383dff373743ff2d2f6aff38383ffa39393874"
|
||||
"38393b013939393f393939e8393939ff393939ff393939ff39393aff3738"
|
||||
"3dff363643ff39393aff38383bff373740ff34354dff1b20acff2a2d73ff"
|
||||
"393939df393939313939399b393939ff393939ff393939ff393939ff3737"
|
||||
"3eff32334eff37373fff393939ff38383bff37373fff313357ff181eb7ff"
|
||||
"1b20adff333450fe3a3a3686393939db393939ff393939ff393939ff3838"
|
||||
"38ff373737ff393939ff393939ff393939ff363643ff38383dff2c2e6aff"
|
||||
"171dbbff171dbdff282b7aff393937c7393939f9393939ff393939ff3939"
|
||||
"39ff474746ff54545cff3b3b43ff38383aff363642ff363643ff31324fff"
|
||||
"1f2399ff171dbdff161dbeff212595ff38383de6393939fa393939ff3939"
|
||||
"39ff3b3b3bff70707dff525496ff353668ff2c2d5eff2b2c63ff252772ff"
|
||||
"1e218cff181db5ff171dbcff161dbeff20249aff37383fe7393939df3939"
|
||||
"39ff393939ff39393bff2d3274ff1c2084ff1c1f8aff1c1f8cff1c1f8dff"
|
||||
"1c1f8eff1a1ea2ff171dbcff171dbcff161dbeff252889ff39393acb3939"
|
||||
"39a1393939ff393939ff353d42ff244a76ff2a3565ff1d2089ff1c1f8dff"
|
||||
"1c1f8cff1b1f97ff181db6ff171dbcff171dbcff181eb8ff2f3061ff3a3a"
|
||||
"358d39393947393939ec393939ff393a3aff373f41ff2f3158ff1c1f8cff"
|
||||
"1c1f8dff1b1f97ff181db2ff171dbcff171dbcff171dbeff23278eff3838"
|
||||
"3fe43a3937383939390639393990393939ff393939ff393939ff27296dff"
|
||||
"1b1e93ff1a1ea1ff181db6ff171dbdff171dbdff171dbdff212598ff3535"
|
||||
"4afd3a39378038393b020000000039393917393939ae393938fe33344fff"
|
||||
"1e2298ff181db4ff171dbdff171dbeff181ebaff1c21aaff272a7eff3636"
|
||||
"46fd3a3937a039393911000000000000000000000000393939173939388f"
|
||||
"373742ed31325aff2b2e6fff282b7aff2a2c75ff2f3062ff353649ff3939"
|
||||
"39e939393784393939110000000000000000000000000000000000000000"
|
||||
"39393a073a3937463a3a36a23a3a36df3a3937fb3a3937fa3a3a36dc3939"
|
||||
"379b3939393f39393905000000000000000000000000f81f0000f00f0000"
|
||||
"c00300008003000080010000000000000000000000000000000000000000"
|
||||
"0000000000008001000080010000c0030000e0070000f81f0000"
|
||||
)
|
||||
30
src/quart_imp/_cli/filelib/head_tag_generator.py
Normal file
30
src/quart_imp/_cli/filelib/head_tag_generator.py
Normal file
@@ -0,0 +1,30 @@
|
||||
def head_tag_generator(static_url_endpoint: str = "static", no_js: bool = False) -> str:
|
||||
"""Generate the head tag for the HTML template files."""
|
||||
|
||||
js = (
|
||||
(
|
||||
f"<script defer src=\"{{{{ url_for('{static_url_endpoint}', "
|
||||
f"filename='js/main.js') }}}}\"></script>"
|
||||
)
|
||||
if not no_js
|
||||
else ""
|
||||
)
|
||||
|
||||
favicon = (
|
||||
'<link rel="icon" href="{{ url_for(\'static\', '
|
||||
'filename=\'favicon.ico\') }}" sizes="16x16 32x32" '
|
||||
'type="image/x-icon">'
|
||||
)
|
||||
|
||||
return f"""\
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="'width=device-width, initial-scale=1.0'">
|
||||
<title>Quart-Imp</title>
|
||||
{favicon}
|
||||
<link rel="stylesheet" href="{{{{ url_for('{static_url_endpoint}', filename='css/water.css') }}}}">
|
||||
{js}
|
||||
|
||||
<script>
|
||||
// inline script
|
||||
</script>
|
||||
"""
|
||||
77
src/quart_imp/_cli/filelib/init.py
Normal file
77
src/quart_imp/_cli/filelib/init.py
Normal file
@@ -0,0 +1,77 @@
|
||||
def init_full_py(app_name: str, secret_key: str) -> str:
|
||||
return f"""\
|
||||
from quart import Quart
|
||||
|
||||
from {app_name}.extensions import imp
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Quart(__name__, static_url_path="/")
|
||||
|
||||
QuartConfig(
|
||||
secret_key="{secret_key}",
|
||||
app_instance=app
|
||||
)
|
||||
|
||||
imp.init_app(app, ImpConfig(
|
||||
init_session={{"logged_in": False}},
|
||||
))
|
||||
|
||||
imp.import_app_resources()
|
||||
imp.import_blueprints("blueprints")
|
||||
imp.import_models("models")
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
return app
|
||||
"""
|
||||
|
||||
|
||||
def init_slim_py(app_name: str, secret_key: str) -> str:
|
||||
return f"""\
|
||||
from quart import Quart
|
||||
|
||||
from {app_name}.extensions import imp
|
||||
from quart_imp.config import ImpConfig, QuartConfig
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Quart(__name__, static_url_path="/")
|
||||
|
||||
QuartConfig(
|
||||
secret_key="{secret_key}",
|
||||
app_instance=app
|
||||
)
|
||||
|
||||
imp.init_app(app, ImpConfig())
|
||||
imp.import_app_resources()
|
||||
imp.import_blueprint("www")
|
||||
|
||||
return app
|
||||
"""
|
||||
|
||||
|
||||
def init_minimal_py(secret_key: str) -> str:
|
||||
return f"""\
|
||||
from quart import Quart
|
||||
|
||||
from quart_imp import Imp
|
||||
from quart_imp.config import ImpConfig, QuartConfig
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Quart(__name__, static_url_path="/")
|
||||
|
||||
QuartConfig(
|
||||
secret_key="{secret_key}",
|
||||
app_instance=app
|
||||
)
|
||||
|
||||
imp = Imp(app, ImpConfig())
|
||||
imp.import_app_resources()
|
||||
|
||||
return app
|
||||
"""
|
||||
9
src/quart_imp/_cli/filelib/main_js.py
Normal file
9
src/quart_imp/_cli/filelib/main_js.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main_js(
|
||||
main_js_: Path,
|
||||
) -> str:
|
||||
return f"""\
|
||||
console.log('This log is from the file {main_js_}')
|
||||
"""
|
||||
328
src/quart_imp/_cli/filelib/quart_imp_logo.py
Normal file
328
src/quart_imp/_cli/filelib/quart_imp_logo.py
Normal file
@@ -0,0 +1,328 @@
|
||||
quart_imp_logo = (
|
||||
"89504e470d0a1a0a0000000d494844520000012c0000012c080600000079"
|
||||
"7d8e75000000017352474200aece1ce90000200049444154789ced9d799c"
|
||||
"5cd575e77fd55d5dbd2fea458d76210909499600631601c21146c1303041"
|
||||
"333193c4c423dbca8c27d8f39971ecfc11e3c4b19de5831dcf4c1cc6c61f"
|
||||
"02820ffed8a34f300a984590a06096c6181b8424848496969084a4def7ee"
|
||||
"ea5ae68f77ef7ba7ababaaababdf52b7eaf7fde71c9dbef5eaaabbeabe73"
|
||||
"ce3bf7dcd0a64d9b408a9a90d05728f911615ba9e4c5c2b65cc956616b49"
|
||||
"91001056b26186390c0a3da6648fb069bd5bd83a953c216cc7943c206cc7"
|
||||
"954cce300762301d1d1d0080b280e741082139c3058b10620ce199879002"
|
||||
"a559c91b84edfa34b68d42aff3744699491732360bfd92395c7b58c97784"
|
||||
"ed15a1bf9ac6d63b87f72301420f8b10620c5cb00821c6c090b070592ff4"
|
||||
"ffa0e4adc276b592e5fe4ca760d161ee75c2765d9a7171a1bfa1e4b3c2b6"
|
||||
"5bc9fd2ecd8b78003d2c428831d0c30a96a54afe6761fb3d25d7f93c9762"
|
||||
"477aa29b5224007c53c97785eda74a3e226ca75c9e179905f4b00821c6c0"
|
||||
"058b10620c0c09bd45df106e17b62f08fd96947124786428aec3c46f08db"
|
||||
"1e257f286c3f177ac283391105bf28841063a087e51e1125b70bdb97955c"
|
||||
"e3ef5488cbc81bfbad2912000e0bfd7b4aee14b6a807732a49e86111428c"
|
||||
"810b1621c4181812e64785929f15b67b955ce2f35c48f0c890ff0125ef15"
|
||||
"b66f2bf9b0b04d7a3aa322851e1621c418e861e5cecd42bf5fc9d5414c84"
|
||||
"1881f4b4b5d7f527c2f645255ff0673ac5013d2c42883170c12284180343"
|
||||
"c2f42c14faff52f2ae2026428a0a9942785ec95dc2a6ebf6cef8331df3a0"
|
||||
"87450831067a58537f075f52f29bc216541f74521a48cffd3625ff42d8fe"
|
||||
"5ec918083d2c42883970c1228418432987843a01fa98b05d15c4440851e8"
|
||||
"f4c3df099bee407bb7b01df1673a85073d2c428831949a87b55de8df57b2"
|
||||
"6892ea3509ab775c7bccd9a6d6127372b50d4aaf4b3807c834c62d5b7ddc"
|
||||
"b155a99f57259d5e74e54a8f24b2f7a78b9659f7c078c8b9174e087dbccc"
|
||||
"6aad3e56e6d806cac35324000c842dbd3becd82e84ad2d9c236525759fd5"
|
||||
"5eff6f844d3f1c7a18254649fde5092166c3058b10620cc51c12562af90f"
|
||||
"c2b6238889e443ad08bd96442700004ba34ee3cac5d17100c0fcc909e735"
|
||||
"f1e04b759c90d1997fb5cbef312242c733912a00c0692501e08348c4d63b"
|
||||
"23d6c720160ab93c0bdfa915fa434aca0363bf28f4091429f4b00821c6c0"
|
||||
"058b10620cc51612b609fd2925af096222999081890ef5d68c8fdbb63563"
|
||||
"2300a6867a21247d999b29c8d077f5d8f014994a4c3d513c555963db8e54"
|
||||
"5a41eac16ac7d65f5e0e0391298e0d42bf43c92e1fe7e20bf4b00821c650"
|
||||
"2c1ed67225f7085be0dd409b546dd39523ceddffcad1215baf8ff1f427af"
|
||||
"09ab87002b8407a6f55bfa1d7ff7ac4adabf53e394e5bd5563e5b9c7cca8"
|
||||
"fb9291c42b4ade226c9dfe4dc53b8cf84b104208c0058b1062102687841b"
|
||||
"85fe9c920b829808e0d44d6d1decb76d570c0f0000ca98342f48e4c38c45"
|
||||
"d1b1291200b60ef60000deadaeb76dafd759fa07154ead5701a2d321af09"
|
||||
"db27957cc7e7b9b80a3d2c42883198e8617d5cc9ddc2d614c444249b8706"
|
||||
"0100978c8fda367a5666a313f61b47066c9bd63bab9cc2f397ea1b6dfd68"
|
||||
"a553715f00c888e32525ef4c6333067a58841063e08245083186d0a64d9b"
|
||||
"829e432edc24f49f2be9f69e5ad76950755817479daaf565134e55fbca09"
|
||||
"2bc1db3c59b47b554b8e532a547cae719e6d2bb004fd98d06f57f2c52026"
|
||||
"321b3a3a3a00d0c322841844a127dd75fb8ca784ade03d2bcda0da9fb64f"
|
||||
"ec599b149d3dcfa99cfcbcf20adbb640ed936b11fbe51a550750ee292c7c"
|
||||
"968e5b7b41ff483c7c3950db000078b6c179363414dcde45f9fdd1dfab9b"
|
||||
"85adc3c7b9cc1a7a58841063e08245083186420c09659b8c6794ac4937d0"
|
||||
"442645e7cba5aa958c64d896ceb8b232ebcfd4907442c226d5d153da1832"
|
||||
"160ef26fb141d56e5d32eefcbd9f6d6c01e06cb00610c45f4f7faf9e15b6"
|
||||
"cd4aeef7792e39410f8b10620c85e461b52bf98cb035a61b6832c7c41d75"
|
||||
"d5b0d56a66a65eecba3b7abff0cefa61256dcb4547c026751457b3b857d7"
|
||||
"24b31fcb45fca34afc9db7f59e07006c1873dad93c3eaf150030ec7f3b1b"
|
||||
"f93dd3df3f79a8f0391fe792157a58841063e08245083186a043c24aa1ff"
|
||||
"b3928b8398885f24445877a4deaacfb9a2bf37efebc585dea34e58ee11b6"
|
||||
"6a714f6a53c1651313f505c32ad109f51e7574dbae9676db76225239ed35"
|
||||
"1ea3bf7fb2b9806e3810f8960c7a5884106308dac3ba5fe80575ba8d1f74"
|
||||
"aa0af84b079df625d58978a6e17931263cba532a517f5624ea5b5452be55"
|
||||
"24e7c3f4ba02a14e25e5b75f386bdb5e68b212f1afd4d5a77d8d87c8efa3"
|
||||
"fe9e067e10313d2c42883170c1228418431021e176a17f3e80f72f18e22a"
|
||||
"5c3b2edcfdf5a227bc57c8aaaff32a51df1572ee5d3a4c9ccf30311064a7"
|
||||
"da5bfaadb3509b6293b6ed99a666004e7d9e0fe8efe92bc2b6d3bfb777a0"
|
||||
"87450831062e58841063f03324d4470f7ddfc7f73482e362bbceda217534"
|
||||
"58d2df104c86173a3cec1161a20e0fdb4498c84336fce39a612755d0a80e"
|
||||
"c7d835afc5b6c94df51ef20f429747881df1e3cd017a58841083f0dac3aa"
|
||||
"10fa634ad6a51b58ca4c88cdaea7abacdaac74ad67fc467a5de794b7d52b"
|
||||
"eee40b95b7d5c80dd6be72e9a875a4dcdde2f7fe58739bad7be86dd50afd"
|
||||
"c742d79d8127e131f4b00821c6c0058b10620c5e87845f11fa551947119b"
|
||||
"932a015f0821613aa2a2136a67c8daea532fee7b8bd476ec4a9f1f1a9422"
|
||||
"2bc4c6e9bbc5fef947557818f73611ff31a1ebeff9df78f986003d2c4288"
|
||||
"4178e561e912863ff7e8fa45cb854aab9dc868b9f3a7a999a12369d00cc9"
|
||||
"9639ea23b540a4ec5b93ee6ee826d391ded67feab3fc909f34b7da368ffd"
|
||||
"5dfd3d7f5cd83c2975a087450831062e58841063f02a24d4fd73aa3cba7e"
|
||||
"d1a25df753a2fafdd2a181f4830b101d089e1155f2832251bf4425e52b98"
|
||||
"94f78cb5aa4eebe6b05306f94283a7e7b9e8eff9ff15b69bd30d9c2bf4b0"
|
||||
"0821c6e0a68775a7d03d595d4b893355d5b66e9287950e99943fac3e724b"
|
||||
"4537fa0656ca7bc2e641a7d6e14285e56dedabf6f44ce24f085daf07bbd3"
|
||||
"0dcc177a58841063e082450831063742429dd9bbcf856b11455f859330d5"
|
||||
"3559855e8f950b3a103ca1aae401a05d25e52f62bd96abc823dceeecbb00"
|
||||
"00e80a2fb26d672b2aa6bdc645bea3e4d3c236e7cdd1f4b00821c6e08687"
|
||||
"f539252f71e15a240d172aada7c6cb478767186926baaffcb8287f58aabc"
|
||||
"2d36097487b06afaf7a9de2edbf683f90b0000516ff61cae52f273c2f6c0"
|
||||
"5c2f4a0f8b10620c5cb00821c6906f485829f4afb9311192992eb521ba58"
|
||||
"4342cd80084d8ea953aa2f1689781e3536775a27c76dfd93037d008027d5"
|
||||
"b1611e71afd01f51723cddc05ca087450831867c3daccf087d891b132199"
|
||||
"e9a988043d05df1955ded65138e50f2bb90fd155ae5227f11c11bb2ade13"
|
||||
"ba4b2c16ba5e377e94efc5e86111428c810b1621c418661b12ea05ee2b59"
|
||||
"471157190e5b7fa66899131e4512a551153e2113f149ebffbf32e4fcdf19"
|
||||
"1ece9d3bfa7b6cfd78bb5509ef516dd657957c50d866b5f39d1e1621c418"
|
||||
"66eb61dda1e4eaaca388270c88866c6dd1d2f0b024dadbd29e1600ac5289"
|
||||
"78963ce44f432c6aeb5b06ad56467b1a9bbc782b5dfd7ebbb03d399b0bd0"
|
||||
"c322841803172c428831cc3624fc2f9eccc230aa2bacf063fb15e76ddbf5"
|
||||
"4b4e00004e0f39472b3d7e602100e057676be10643153224ccbb58d87826"
|
||||
"d254c4af1215f1e50c0ff3e6ba21abfafdedda3adb763eecfad10f9f173a"
|
||||
"4342424871c2058b10620cb9f87acb84fe49af2662121f5b300200d87374"
|
||||
"9e6ddbb5df0a056fbfb4cfb67d75f33b00809f1d721eaafef49d96bcdf77"
|
||||
"acbc7ce64125c6b80a0f3bc5169e15223c0c313c9c15baffd8d641e773fc"
|
||||
"58739bdb6f73abd0f5c5bbd20d4c851e1621c41872f1b0e446672e70005e"
|
||||
"3e5597f1678fbce5dc8de2eae6fe1fd71eb16d1786d6dbfa8b271a66f5be"
|
||||
"1365fcf567625824e2cf888fe962f689cf8b35a343b6beb4cefa9c9e8a54"
|
||||
"661a3e5b6433f9df53f2fbb9bc90df0042883170c1228418432e21e1ef7b"
|
||||
"3e8b02a3b5dad9aa70f7fa830080f5ad4edd53a3f28c07269cd7ecefb2fa"
|
||||
"083d72c009f9f61eb3b637fcce1a67dc5d1b4ed8fa8b272e9bd5bc62de6c"
|
||||
"482d3a7a42ce7d5807316d0c0df3e6e641ab6fd643aded5e5cfe0f956448"
|
||||
"4808292eb279581b945cebc7440a89cf6d7ccbd6f777590d557f76c42961"
|
||||
"a8afb00e34fd9dd5076cdb0d8bc700003515bfb66d4f1ebb7cdab593c9fc"
|
||||
"bd243ea09f3d1f2a6fab56fcf66a92b3ea6852f25c3c6e95f12c9c74ce41"
|
||||
"75f110d6ab9494ebcca14c83e96111428c810b1621c418b2858477fa368b"
|
||||
"02e370cf225b7ff6f8c28ce3de7fe31a5bbfff13bf040044ce383fffc385"
|
||||
"9dd35e73faa053c3b566c43ab6eb7db1d1345bb0126677cd59a37f632745"
|
||||
"227eb5fa3d7293f4ecd8343c68eb8fcfcb7fc746067e57e8dfca34881e16"
|
||||
"21c418b27958b766f95951f3dc89cc5e956432eeacf77b9f5d0000889e77"
|
||||
"f6b4adbfd64ac4779d768e4e3adae1242b37c4adfd5a2d134e7d4447b375"
|
||||
"e74a77efa787953f51380f3b4e87acbfd1b2642ca8e918c98631a7fa7d4f"
|
||||
"a3f3106ad89d1d18b7099d1e1621c47cb86011428c2135246c16fad57e4e"
|
||||
"a4909888e7b68e5f3c3a62ebd1fee9ad5f0ebe5e3d456662e1f8a8b86615"
|
||||
"00e078cdf42ea53571566bbb41bfda31d024eed78daccd9a91f284f33bba"
|
||||
"4c7cf65fadab77e3f257095daf43bda983e86111428c21d5c3ba51e8ec16"
|
||||
"370317abb2042fae99cec36a8a46a7d948fe9c09391ff13a963acc8a8da3"
|
||||
"ce67df250f4bae377a1dda9d3a881e1621c418b86011428c213524bc2e90"
|
||||
"59184a636c72e6412e5c5357103579f07ea58cfc6dea4dd2ec509a1b0ba3"
|
||||
"63b6de16b3ead9badc3b0e4caf430c090921e692ba245e1fc82c0c453e08"
|
||||
"4fb7f2dff5df4e030076fd6071ced78c85a65fa94525db2b12bcfb7b856e"
|
||||
"fad722feaad5dc599013978cab1d1dee24df812ceb103d2c42883170c122"
|
||||
"841843185317add935192f717a2b9c638fe647c7a7fd3ce750505410978d"
|
||||
"59eef5eade1edb56cb03547de38c28075a056e8ece855513d667f635f742"
|
||||
"42bd0ec9b529916a20849082860b1621c418c20056887f4fdf0f423272a6"
|
||||
"a6c6d6d38584e990cf9dca541fac642462db226a13ee47cf9eb46df12aeb"
|
||||
"7d9269b6eb1077191147a90da8fb3937466767d984b5795f262ee6f83c5b"
|
||||
"7fd0e5da7414a0874508318830808f043d09533959ed78586b0707000055"
|
||||
"696aa59200cac79dcae0a4aa582f8f5a1e5622e6b49f49282faa4cb49c29"
|
||||
"577a3ce224f993ee5515930c9cd31e56d64efb24a21e1acd17c7807de8ce"
|
||||
"3160726d3a0ae476f233c98332b140a5120a57d88b1621247718127a40b6"
|
||||
"c58a10923f6100ab829e84a9c44482f640432300e0ea7367320db7e94708"
|
||||
"0847d0ac42c2504cf4b90ad5657815109a7012fbc970e671c41dc6d5df77"
|
||||
"809d49736241ccf59070dada440fcb45665aac3e686db7162b42485e8401"
|
||||
"2c0f7a12c540674ded8c4df0bb375e8eeeb77e6dffbb79541d9b54261e08"
|
||||
"2732dfc1a77862c437ce4b0f8b09f88cb4cb8eb8e281d41c589e6aa087e5"
|
||||
"22bb56accefab3f73b4ff8381b428a0f3e2574996c8b1600acbd69350ebd"
|
||||
"7804bfffd98fe1c8b73bfd991421454218c0d2a02751ecfcf99d2b503fcf"
|
||||
"392977f3e5cb30d4d787d5f7fe2e0edff71412621375683273b94388c77c"
|
||||
"05c29878b832a482927a26dfa7d11c777db3f8b4b58921a1c7a42e560030"
|
||||
"316a15828e9def0f624a84184b18c0fca027612a2175e74d66e94cf9cddd"
|
||||
"c7f19dcf5e39c516557b08abdb9b3059df8832f1faf221ab623e51e3942d"
|
||||
"247485bbb8d34f2a7d4224ecc7caad087f54b4a319128f97c7943d2eaea3"
|
||||
"df3b2ce650a912ff95e20140a5eadb5d256daaaabf5ad88abd2b6a97f6b0"
|
||||
"987c9f4683fbc5d0eda906e6b07c60187508df7aaffd6f59a172c51f00fb"
|
||||
"6eb8d4ff491162205cb0e64836ef0a00beb5eb85d95fb32232f320424a90"
|
||||
"308096a027612a332d569a6383c358d9508781e824bac7ad70b0b5aa128d"
|
||||
"910abc76cb55a8971ba35316abb2d161948d8d0000fa6a9c8e8eaf2d590e"
|
||||
"606a8856af5cf24691b86f1f77aae39b26adf71e1049fe0fabaa000067ab"
|
||||
"9c0dd84373d8585da17e27753127012b8f2ed3f679a266a759b5e6a930e0"
|
||||
"d087211d8a8b02e04a03e6ed07f5eea7039a530d4cba7bccd7efda8a7a95"
|
||||
"476a8c546065431d5636d4a13162d9ea7b2e647dbdcc651152ea84916615"
|
||||
"23ee32343989f9d595536cfb3fb62ce7d727aaad96331fd63a1e96f68286"
|
||||
"c4b8aec854efacaaaa0ae3c2c32a579e40abf06ee6abfd89d7f774d9b6c9"
|
||||
"32eb3e7652340c3ca52a97a365d9ef71fa61409f48f6f7cdb0af4cfb2af5"
|
||||
"c22b6b51fb2cdbd4030a39d7742d7cfca657dceb17ccb55d5d91e0c10397"
|
||||
"696b1373583eb0b2c1f19266b350a572e9f000deab6bcc79bc5cac082906"
|
||||
"b860f9c05c1629428843185cb43ce3aee3475cbfe6a5c303336effc984ae"
|
||||
"bf3a5fe984a75adfdfe0786efaa4e9a5634ed7d3ad17ce0100ce8a4dadef"
|
||||
"d75a9ee3f01cbb9fea94f5a0b88ed64f88b054878e2d22a45da41e582c19"
|
||||
"1db16d7e848cbde284ee8b44d57b08a59b80afc8b2713fdf4ba61a9874f7"
|
||||
"082f162b424a9d308086a02751ead4bc781000307ad3fa9cc6df75fc48de"
|
||||
"5e562ef4a8e47d8f48e26b0f6cb9f064aeefed06000c849d1be1bb6adca0"
|
||||
"073de7b5efd22de6a575e9215e24ca4496ab6d500b448f7c373a92c95d73"
|
||||
"4362e7404309973894bbef5d4e3b9995e1a047ec5ab13aad97b5e1cd9369"
|
||||
"465b1c9be5c24548a9c105cb436a5e3c38e509e14cd863df3c896383c300"
|
||||
"322f5e5e7b59841422a14d9b3695ae0feb11db1f7c62ca42d55e33f5be70"
|
||||
"7e34f7361cc70687b37a5c412e5a3a105a2292f36b8606010003a262ff80"
|
||||
"08d7e4c66c3fa911ad79568f5837838b479c2ab6f23984724de2b5cb92ae"
|
||||
"b758318698a8d1fbcb85cb5dbd764747470860d2dd53da6bc2d316abd9b2"
|
||||
"b2a1cece71a583c97d524ad0c372993ffbc973a82e2fcfb850cde45d551f"
|
||||
"fc276c7c77279213499c59f70738f3d14f03b03cad9d3bb6655ca00a253c"
|
||||
"d45e974cce5f32ec7832a75599c2e15ac70395ed6efca45a785deb9467b8"
|
||||
"7c74d8b6e53a2b79d75f9f88295be97dad46cb9dcffcdf2c70b72f283d2c"
|
||||
"0fd8fee0131917abf3a3b1ac8b5538368c6b9fde8acb3a1f40623081e444"
|
||||
"120bdffa31aebcffb629e3322d4cf4b44829c005cb25b63ff844c69fe592"
|
||||
"b3ba7aefed0080e4e8d43d876555c047bf772b5636d4d9ef916dd1e2c245"
|
||||
"8a99d0a64d9b06c4bf599395273ad19ec9bbcac6752f6e46321a4162c86a"
|
||||
"f192189c5a311cef4e201903defada73387ae62cdedcb10d831515d874f6"
|
||||
"838cd72c941011989ad0fe880abdda45add4be46ab85b4acc00f8a26d19a"
|
||||
"e7cafe5e00c0bcc9dc8f575b9eb4c2cc523c70b547b42dfadfed8bdcb8a4"
|
||||
"9d4be8e8e86800e861b94665797ebfcaeb5fb976cabf53172b4d52352d08"
|
||||
"d537607d97b54da663e1928cd7a5a7458a112e582eb0fdc127b0b8d6da63"
|
||||
"b77e62a6e3541d6ee8b81a884f7dcc5fd630fd4f52deead8c229ed5d4e36"
|
||||
"b5e064137b3092d2208ca9bb0cc81cd812d98a246ab12eb605ef86f7661d"
|
||||
"bbf9d797230955ab142f47323ef3bd63e3976e01bebf07873ef3c758fbb3"
|
||||
"c70000af37b702000e35347ab21dc60de453c07daa266bbee8707a655f0f"
|
||||
"00a0abb2cab6bdddd8040088f9fc04b15ff4eedadb669d81b05685b10070"
|
||||
"a93a2424d3ac06d54f726f02543cf487b3f73dcb8369a75ad0c3f28875b1"
|
||||
"2d199f0c6e7e7b2392f1340594e1cc798fe484131646b6dfe3d63409318a"
|
||||
"30805ef16f761fcd83950d75d812d93acdbe25b215e87c0f482481441992"
|
||||
"d10a24a215b667958c974df3ac92f1dcea779e9f7fd1dc271e2017c406e6"
|
||||
"7f559ecc95fd7db6ed66d5cee6cd794eb8db1df1f7700e7dfb3858ef3c8b"
|
||||
"ea5647ae5da3bc42008888763603aaedcce2126c39d35deeba87df9b6aa0"
|
||||
"87e502c70687b1379ae1749ce5d6115ec978797aaf4afd2c3597954a64b5"
|
||||
"f3a79acdfe44428a092e587e132f07e265e917a85859d68c62921d8f4989"
|
||||
"1306d023febd2aa889142dc70f01004215934846539292693cabe4c4cce1"
|
||||
"c3f7befc47ae4daf10d0075b74343be19fdea0bc599c2af46ebd95ca3e5c"
|
||||
"37ad4d926fe85ab1175b9d03d36f100778d4c5ad3bcea87858505b223db2"
|
||||
"fadc4fba3324f49d156b6dd5ca594df7ac742e2b530d16008cffd2c9932c"
|
||||
"bde21a77e74888218401643f188fccc8ce1ddbf0ad5d2f606ff485b4c977"
|
||||
"00488e3b55c0c90c650ca1da10922399efc6071edd836383c33877f8c0dc"
|
||||
"275de01c519ba3fb459b9a6b5587537950ecaf9bac2a79bf3750cb3ef6ba"
|
||||
"fc01003ede6d7d9d86279d9b8fd395beb8393fc3716ef95c32d5400fcb25"
|
||||
"466299934f72b19a62cfe071cd44292c5884a4830b964b9c1bcd2123ae13"
|
||||
"eee9c892709f783b8efd3fda0300d8f3777f91e70c09319f308053414fa2"
|
||||
"18d8b9639bd54da1e105dcd4b502c9452bd38e0b452611c22412f17284aa"
|
||||
"27108f5a214fa86602a11a207e6eaa5b3d793281f7fef2510056f9c48787"
|
||||
"de4128a0fe514120ebb574a25b1f7e010037765b09ef575b5a6ddb4ca753"
|
||||
"bbcd8478bf975bda0000cd6abf2700b4c78afb64e884aaee3f1d717df3fa"
|
||||
"b4b5a930f77218ccb1c161a0ed38b6742e059249201e025081c45855daf1"
|
||||
"d9b6e4c4ce2530b46823a2cded188bc7b173c7368f664d8819840174063d"
|
||||
"8962c1f6b200ec5d780a5bce38dd1412c39953afe1666b7f5aacd77a6c9f"
|
||||
"1877ca1b8efdd7fbd03d3e81fff399dbbd9ab631e844f75e5152b0599514"
|
||||
"dc284a0b7ea1bc1cc07f6f6b4cf5acdfd3e2cc71edf9d3008ab70be98588"
|
||||
"75331e77dff3ef4c353087e5323b776cb34fbcd9bbc8e95755d6389876bc"
|
||||
"5eacb45eb9ae1b88591fecb7bef61c0060203a6d0f2821250943420fd8b9"
|
||||
"631b565eb705dbffc79f61ef522b0cbf71ff7a84aac791cc101a4a3ab77e"
|
||||
"193debb6e2d8e0307efca54f63529c4a43482913067034e8491423c75edb"
|
||||
"8bbf7def003e75df8f2cc306e7e49beb5fb916a1f2e945a2af6c7ac379bd"
|
||||
"3a74824c4786793afc932161baf0d0efd0b04fd42475a94e9ced93c5b9b7"
|
||||
"aab3b27ae641f9316d6da287e52123bd5df6a2a3735b2b1beaf0ea0daf67"
|
||||
"7ccdffbc6d13c6c6c6d0d6d696710c21a54a1800ab107d402f5ceb6efe77"
|
||||
"e839d589f3470eda8bd8ce1ddb90482450565686baba3ad4d5b11b43ae4c"
|
||||
"2acf4926daa587757d8f5502f152abf3f384cf6521c7aaac6eb4c5ea61bd"
|
||||
"5735739a234fa6ad4df4b07ce6dd7f79dad665c857e673c8428889f05b42"
|
||||
"0831863080e3e2df23422f953d9ba40898141eea2b223cfc2db519f9f241"
|
||||
"a77ce437aa5fbc5f9cf1b953aa1f8c89eea29d95ae87847a1d3a9efa037a"
|
||||
"5884106308c3695d0d00fb847e9dcf7321c415e4debed7d4a9429f107bfb"
|
||||
"cea9267c67ab3c7b1c3f850fdcdf63173887ab9c00cc839d927a1d9a56fb"
|
||||
"430f8b10620c5cb00821c6905ad6f09ad0191212e319521ba6df6e724eb0"
|
||||
"bb7ca01f007041248bbd3cb0b54f6d881e1189eadab8d9e717ffa6d6d35a"
|
||||
"c1d732fd801e1621c418b86011428c2135247c55e85ff173228478496775"
|
||||
"8dad2f1eb5ba5f5ca28e120380433e1c1d7636e284a0978c0d6719599874"
|
||||
"5738f3eff4f6c92743424288f9a47a58bf10ba2eaf98fdb12e841430fb54"
|
||||
"a5bb3e920b708e15f3f2b8b00b61a7e2fd12cfdec53bdeac75bc500f7aa7"
|
||||
"ca72ae97320da287450831062e58841063480d09e559f6bf52f25a9fe642"
|
||||
"882fe8daac1e91385e3061f5aa3aede1769d6ef74f46f68571553ff6a6b7"
|
||||
"b5576f0abd37d3207a58841063c8d6c0ef1925e96119c282898969b60f2b"
|
||||
"8b6fe3ad5b7c50e3943ab48f7bef615d089bd92fb3a3d63a7e6ec2db4ead"
|
||||
"4fcf3c841e1621c420b86011428c219b8fba5bc96ffa3111921fe549a722"
|
||||
"e6b2fee9b9ca0bf32f9a36b62ae1b4191a551b73bddcfc5ba85c1049f74b"
|
||||
"87d21f74ebeafb85cd49ba4f9439e597affbb00b00c0e3b90ca287450831"
|
||||
"866c1ed67e250f09db5a0fe742f2608dd80f5797a665c96f5f703a6dea96"
|
||||
"2693c29b8aaa3be9b0b8fb1f68b092ac7d863e86cf1579b86a3831fd605b"
|
||||
"b71917ef37a44a05ea0bb4cdcc2feae7d9faa8b7273ae972867773194c0f"
|
||||
"8b10620c5cb00821c6904b61c84f84ce047c81501bb7f68aae191a98619c"
|
||||
"1372ec6bb4dcfca3a26239698f73f69e6e541d394f8b3aa50f7c3ab02128"
|
||||
"c2490fb6f366a1b7c24af8175a4838a0366877f893680780476733981e16"
|
||||
"21c41872f1b0e40af80d25b9d005cce5fd7d00a69635ccc47c55cdfd7e9a"
|
||||
"3d6123e5e23176730b0060536f8f6dd349e2fe224ac4cbdf9ddf1fe83ef5"
|
||||
"fb5ce6f3fbcec4f38d56effb496fcb5c2685fed3d9bc900b0f21c418b860"
|
||||
"11428c219790f0a4d0f72879ab077321b3e0d59656cfaead0325dd991300"
|
||||
"aeeab3aae8ffadb5cdb3f7f51b59f13fe66dadd134faca0b6723f4fbd54e"
|
||||
"8ae01dd1fbde439e157ad76c5e480f8b10620cb35de61f50921e56092013"
|
||||
"f13a09db32e9e44b7b0c4fc057080f6bd8e77d7e85b08b4057fa3fd9d4e2"
|
||||
"f75b3f94ef0be96111428c810b1621c418661b123ea5e4fbc266e2894564"
|
||||
"9674abcea5ede363b6cdf4903022eab0867cfebff495077f7ade734dd603"
|
||||
"947effe67254c9a7b28eca023d2c42883170c1228418c36c4342fd58e53b"
|
||||
"c2f62397e6420a983115362c50db7b8a81ba98f3c473d8e70322fa03aac3"
|
||||
"3a226aaedeaca9f5fbedbfab64decdc7e86111428c21df655e6e88feba92"
|
||||
"4be6381752c0e8cea495a20d8de9d4c59cd62e677d6e9f33a43cd6249c4d"
|
||||
"c62178d7e26650b58df9d93c6787844f0d754e0bfd91b95e8c1e1621c418"
|
||||
"b86011428c21df90501e31fc574afe708e732105cca04a4a37c426671869"
|
||||
"0e4dd1a8adbfe75f874d00800eac87450d94dbdd47e322dcdcd56cd55c8d"
|
||||
"f8bcc91bc0b7853ee72736f4b00821c6e0c6b355bd91f14f848dd5ef4586"
|
||||
"3e7075543c8e6f541ba1070cab78d79d462b92ced3f5a8ff9e070060b0dc"
|
||||
"f9ddb9ed613dd33cdfd64f8a43637d4257b5e7bdd1391df4b00821c6c005"
|
||||
"8b10620c6e84843a0bfba7c2f6840bd725054867ad531dbd7c6c1400b0af"
|
||||
"a231a8e9e4c582092bf7db5f11097826c0a0e8c3b5283a966564eebc5e6f"
|
||||
"1d24f186ff95ec92af2ae9ea531a7a58841063707343d36ea1ff8b9237bb"
|
||||
"787d5200748abbf66f755d00004412c127af67c3b2911100c0719f4b19d2"
|
||||
"e1d69ec2c335ceffe559d18bdf67fe55e8bb338e9a0385ffe92248ab5bca"
|
||||
"000005d049444154841005172c42883178d5e3e21e25f7095b9547ef457c"
|
||||
"44f605d9af428fcb06fa6ddbafe635fb3ca3dc6812876734aa6afd7395be"
|
||||
"d7264d632edd3ecf469c0ddbff4f6c6acebb774bfee80af63ff6fa8de861"
|
||||
"11428cc12b0feb8892df14b6bff6e8bd48407445acb280a649a73c60d5c8"
|
||||
"3000e0686d5ddad7f88dde4d77657faf6d3ba692ed3eb557c9ca401e8d03"
|
||||
"7b2a2ccff0d1d676dba68f610b08fd3d3f9275940bd0c322841803172c42"
|
||||
"883178dd58fabb42dfa6e4551ebf27f199f745f8b76e681000b06678c8b6"
|
||||
"1d0eb0dee90af54040064cef075b013e855c93ee7d6127ec7ea8f5220081"
|
||||
"b48a91bc29f4ef661ce532f4b00821c6e0b58725f711ddade46f84ad706e"
|
||||
"75c415dead6f00005c3c3a62dbaeefe9b6f5830dd6bec37e975bd2c86afb"
|
||||
"2bfbfb6cbd69d26ad2f76fad4eab9544b009ea29f484b3ff1eb467f550db"
|
||||
"02db3618dc21ac2342ffb4d07debea480f8b10620c5cb00821c6e0e7698e"
|
||||
"ba46e38bc2f6b08fef4f7ce484486cf7886e9797ab704d0635e7aaac4d10"
|
||||
"7da2ddcb880a7b62227c0bab4ea1b5e2a8b18b54ab9865234e92ff7ca553"
|
||||
"01fe629b55ab3451a09bb2c7c5ff4f1fc5150d39737d58d55a0518064abe"
|
||||
"2474cf6baed251987f454208494310e765ef14fa0d42ffbccff3203e3128"
|
||||
"aab97fd16a9dde324feced5baa1a01ae1f74f624d6abfd7edaab029cc35c"
|
||||
"65d3bb6eb51f706fdb4569dfcf24f6a91631af8a3290804b1734ffa864e0"
|
||||
"115141fc3608212417b86011428c2168dff91ea17f44c96b829808f1973e"
|
||||
"5187d567f78437ab37bcdb3cdf5050ffff5f0afd9e8ca37c861e1621c418"
|
||||
"b86011428c21e8907042e8772af92b615bece35c0821c06925b709db44ba"
|
||||
"8141400f8b10620c417b5892734ade266c2f2b5950d948428a8c01a1ebef"
|
||||
"df87414c6426e86111428c810b1621c4180a2924d4ec17fa1d4a3e276c35"
|
||||
"3ece8590626654c93b846d7fba8185023d2c42883114a28725d149f73b85"
|
||||
"ed492579302b21b3674ce8ba74e1e574030b117a58841063e08245083186"
|
||||
"420f09352f08fd934aee16b6261fe7428889e8666332bdf2521013990bf4"
|
||||
"b00821c6608a8725d177858f0b9b2e7b580042884656abebc8e49d2026e2"
|
||||
"16f4b00821c6c0058b10620c2686841ae9da5ea7e41e615bede35c082924"
|
||||
"f4115cb7085b6700f3701d7a5884106330d9c392742a298f0dfbb99257fb"
|
||||
"3b154202e10da1ff7b25cf0731112fa187450831062e5884106328969050"
|
||||
"d325f41b95bc5fd878ba342926fe51e85f14fab8df13f10b7a5884106328"
|
||||
"360f4ba24ffad8216caf09fdef95acf5673a84cc8911a1ff77251f0a6222"
|
||||
"41420f8b10620c5cb00821c650cc21613aa40bfd8a928f09db553ece8590"
|
||||
"5cd0070bdf2d6c47d20d2c05e86111428c810b1621c4184a2d249468b7fa"
|
||||
"3a61fb5325bf2e6c3cec82f885ae9ffa96b07d47c9499fe75290d0c32284"
|
||||
"1843297b589a98d0ff5ac97f12b61f2ab9c59fe9901263afd0bfa064c926"
|
||||
"d567821e1621c418b86011428c8121617aa44b7e9392db84ed3e2557f933"
|
||||
"1d52241c15ba7ec0f344101331157a58841063a087953bf24ef8b4929f15"
|
||||
"b67b955ceccf744881735ae87fa5a4dc6911f5712e45033d2c42883170c1"
|
||||
"2284180343c2fcd0eefc03c2b653c9cf09db97956472beb891c9f4ef29f9"
|
||||
"b0b0156d0750bfa187450831067a58eea13b9cfe40d8b4077687b07d41e8"
|
||||
"bfad246f1c854d42e8cf2b29bdeb27338c252ec32f0a21c418b86011428c"
|
||||
"8121a1b7e8f0e09f854dea4b959489fa4f29b9ceab4991ac1c12fa2e2565"
|
||||
"fdd4291fe74252a087450831067a58c1a2efd6df1036ad6f10b63b95bc55"
|
||||
"d8ae56b2dcf559152771a1bfa1e4b3c2b65bc9fdfe4c87e4033d2c428831"
|
||||
"70c12284180343c2c2657f1a5df6fa6e56f20661bb51c94dc27699d08bf1"
|
||||
"946b7d22f23e61eb10facb2912007a3d9d11f10c7a58841063a087652eda"
|
||||
"4b9055d64fa619276f4a2b94dc286c17a7480058ae64abb0b5a448c0f9fc"
|
||||
"d4679b288021a1eb1efa3dc2a6f56e613ba9e471613ba1e43bc2a67fce0a"
|
||||
"f312801e1621c418b8601142082184104248c9f2ff01ac2d3c02891e3dae"
|
||||
"0000000049454e44ae426082"
|
||||
)
|
||||
168
src/quart_imp/_cli/filelib/resources.py
Normal file
168
src/quart_imp/_cli/filelib/resources.py
Normal file
@@ -0,0 +1,168 @@
|
||||
def resources_cli_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
|
||||
|
||||
@app.cli.command("show-config")
|
||||
def show_config():
|
||||
print(app.config)
|
||||
"""
|
||||
|
||||
|
||||
def resources_context_processors_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def example__utility_processor():
|
||||
\"""
|
||||
Usage:
|
||||
{{ example__format_price(100.33) }} -> $100.33
|
||||
\"""
|
||||
|
||||
def example__format_price(amount, currency='$'):
|
||||
return '{1}{0:.2f}'.format(amount, currency)
|
||||
|
||||
return dict(example__format_price=example__format_price)
|
||||
"""
|
||||
|
||||
|
||||
def resources_error_handlers_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
|
||||
from quart import render_template
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
async def bad_request(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=400,
|
||||
error_message="The request is invalid.",
|
||||
), 400
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
async def unauthorized(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=401,
|
||||
error_message="You are not authorized to access this page.",
|
||||
), 401
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
async def forbidden(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=403,
|
||||
error_message="You do not have permission to access this page.",
|
||||
), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
async def page_not_found(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=404,
|
||||
error_message="The page you are looking for does not exist.",
|
||||
|
||||
), 404
|
||||
|
||||
|
||||
@app.errorhandler(405)
|
||||
async def method_not_allowed(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=405,
|
||||
error_message="The method is not allowed for the requested URL.",
|
||||
), 405
|
||||
|
||||
|
||||
@app.errorhandler(410)
|
||||
async def gone(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=410,
|
||||
error_message="This page is no longer available.",
|
||||
), 410
|
||||
|
||||
|
||||
@app.errorhandler(429)
|
||||
async def too_many_requests(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=429,
|
||||
error_message="You have made too many requests.",
|
||||
), 429
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
async def server_error(e):
|
||||
return render_template(
|
||||
"error.html",
|
||||
error_code=500,
|
||||
error_message="An internal server error has occurred.",
|
||||
), 500
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def resources_filters_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
|
||||
|
||||
@app.template_filter('example__num_to_month')
|
||||
def example__num_to_month(num: str) -> str:
|
||||
\"""
|
||||
Usage:
|
||||
{{ 1 | example__num_to_month }} -> January
|
||||
\"""
|
||||
if isinstance(num, int):
|
||||
num = str(num)
|
||||
|
||||
months = {
|
||||
"1": "January",
|
||||
"2": "February",
|
||||
"3": "March",
|
||||
"4": "April",
|
||||
"5": "May",
|
||||
"6": "June",
|
||||
"7": "July",
|
||||
"8": "August",
|
||||
"9": "September",
|
||||
"10": "October",
|
||||
"11": "November",
|
||||
"12": "December",
|
||||
}
|
||||
|
||||
if num in months:
|
||||
return months[num]
|
||||
return "Month not found"
|
||||
"""
|
||||
|
||||
|
||||
def resources_routes_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
|
||||
|
||||
@app.route("/example--resources")
|
||||
async def example_route():
|
||||
return await "From the [app_root]/resources/routes/routes.py file"
|
||||
"""
|
||||
|
||||
|
||||
def resources_minimal_routes_py() -> str:
|
||||
return """\
|
||||
from quart import current_app as app
|
||||
from quart import render_template
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
"""
|
||||
49
src/quart_imp/_cli/filelib/templates.py
Normal file
49
src/quart_imp/_cli/filelib/templates.py
Normal file
@@ -0,0 +1,49 @@
|
||||
def templates_minimal_index_html(
|
||||
head_tag: str, static_path: str, index_py: str, index_html: str, init_py: str
|
||||
) -> str:
|
||||
return f"""\
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
{head_tag}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="display: flex; flex-direction: row; align-items: center;
|
||||
justify-content: start; gap: 2rem; margin-bottom: 2rem;">
|
||||
<img style="border-radius: 50%"
|
||||
src="{{{{ url_for('{static_path}', filename='img/quart-imp-logo.png') }}}}" alt="quart-imp logo">
|
||||
<h1 style="font-size: 4rem;">Quart-Imp</h1>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 2rem; margin-bottom: 2rem;">
|
||||
<div>
|
||||
<p style="margin-bottom: 0;">
|
||||
This template page is located in <code>{index_html}</code><br/>
|
||||
with its route defined in <code>{index_py}</code><br/><br/>
|
||||
It's being imported by <code>app.import_app_resources()</code>
|
||||
in the <code>{init_py}</code> file.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def templates_error_html() -> str:
|
||||
return """\
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>{{ error_code }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{ error_code }}</h1>
|
||||
<p>{{ error_message }}</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
902
src/quart_imp/_cli/filelib/water_css.py
Normal file
902
src/quart_imp/_cli/filelib/water_css.py
Normal file
@@ -0,0 +1,902 @@
|
||||
water_css = """\
|
||||
/**
|
||||
* Forced dark theme version
|
||||
*/
|
||||
|
||||
:root {
|
||||
--background-body: #202b38;
|
||||
--background: #161f27;
|
||||
--background-alt: #1a242f;
|
||||
--selection: #1c76c5;
|
||||
--text-main: #dbdbdb;
|
||||
--text-bright: #fff;
|
||||
--text-muted: #a9b1ba;
|
||||
--links: #41adff;
|
||||
--focus: #0096bfab;
|
||||
--border: #526980;
|
||||
--code: #ffbe85;
|
||||
--animation-duration: 0.1s;
|
||||
--button-base: #0c151c;
|
||||
--button-hover: #040a0f;
|
||||
--scrollbar-thumb: var(--button-hover);
|
||||
--scrollbar-thumb-hover: rgb(0, 0, 0);
|
||||
--form-placeholder: #a9a9a9;
|
||||
--form-text: #fff;
|
||||
--variable: #d941e2;
|
||||
--highlight: #efdb43;
|
||||
--select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml \
|
||||
version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1'\
|
||||
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3\
|
||||
.org/1999/xlink' height='62.5' width='116.9' fill='%23efef\
|
||||
ef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 \
|
||||
L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,\
|
||||
7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5\
|
||||
60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.\
|
||||
3,1.6Z'/%3E %3C/svg%3E");
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-color: #040a0f #202b38;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--background-body);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', \
|
||||
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', \
|
||||
'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', \
|
||||
'Noto Color Emoji', sans-serif;
|
||||
line-height: 1.4;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 0 10px;
|
||||
word-wrap: break-word;
|
||||
color: #dbdbdb;
|
||||
color: var(--text-main);
|
||||
background: #202b38;
|
||||
background: var(--background-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
button {
|
||||
transition: background-color 0.1s linear,
|
||||
border-color 0.1s linear,
|
||||
color 0.1s linear,
|
||||
box-shadow 0.1s linear,
|
||||
transform 0.1s ease;
|
||||
transition: background-color var(--animation-duration) linear,
|
||||
border-color var(--animation-duration) linear,
|
||||
color var(--animation-duration) linear,
|
||||
box-shadow var(--animation-duration) linear,
|
||||
transform var(--animation-duration) ease;
|
||||
}
|
||||
|
||||
input {
|
||||
transition: background-color 0.1s linear,
|
||||
border-color 0.1s linear,
|
||||
color 0.1s linear,
|
||||
box-shadow 0.1s linear,
|
||||
transform 0.1s ease;
|
||||
transition: background-color var(--animation-duration) linear,
|
||||
border-color var(--animation-duration) linear,
|
||||
color var(--animation-duration) linear,
|
||||
box-shadow var(--animation-duration) linear,
|
||||
transform var(--animation-duration) ease;
|
||||
}
|
||||
|
||||
textarea {
|
||||
transition: background-color 0.1s linear,
|
||||
border-color 0.1s linear,
|
||||
color 0.1s linear,
|
||||
box-shadow 0.1s linear,
|
||||
transform 0.1s ease;
|
||||
transition: background-color var(--animation-duration) linear,
|
||||
border-color var(--animation-duration) linear,
|
||||
color var(--animation-duration) linear,
|
||||
box-shadow var(--animation-duration) linear,
|
||||
transform var(--animation-duration) ease;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h6 {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
b,
|
||||
strong,
|
||||
th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
q::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
q::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #0096bfab;
|
||||
border-left: 4px solid var(--focus);
|
||||
margin: 1.5em 0;
|
||||
padding: 0.5em 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
q {
|
||||
border-left: 4px solid #0096bfab;
|
||||
border-left: 4px solid var(--focus);
|
||||
margin: 1.5em 0;
|
||||
padding: 0.5em 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
blockquote > footer {
|
||||
font-style: normal;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
blockquote cite {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
address {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
a[href^='mailto\\:']::before {
|
||||
content: '📧 ';
|
||||
}
|
||||
|
||||
a[href^='tel\\:']::before {
|
||||
content: '📞 ';
|
||||
}
|
||||
|
||||
a[href^='sms\\:']::before {
|
||||
content: '💬 ';
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #efdb43;
|
||||
background-color: var(--highlight);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px 0 2px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
a > code,
|
||||
a > strong {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select,
|
||||
input[type='submit'],
|
||||
input[type='reset'],
|
||||
input[type='button'],
|
||||
input[type='checkbox'],
|
||||
input[type='range'],
|
||||
input[type='radio'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[type='checkbox'],
|
||||
[type='radio'] {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
input {
|
||||
color: #fff;
|
||||
color: var(--form-text);
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #fff;
|
||||
color: var(--form-text);
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: #fff;
|
||||
color: var(--form-text);
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
select {
|
||||
color: #fff;
|
||||
color: var(--form-text);
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #0c151c;
|
||||
background-color: var(--button-base);
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
background-color: #0c151c;
|
||||
background-color: var(--button-base);
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
input[type='reset'] {
|
||||
background-color: #0c151c;
|
||||
background-color: var(--button-base);
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
input[type='button'] {
|
||||
background-color: #0c151c;
|
||||
background-color: var(--button-base);
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #040a0f;
|
||||
background: var(--button-hover);
|
||||
}
|
||||
|
||||
input[type='submit']:hover {
|
||||
background: #040a0f;
|
||||
background: var(--button-hover);
|
||||
}
|
||||
|
||||
input[type='reset']:hover {
|
||||
background: #040a0f;
|
||||
background: var(--button-hover);
|
||||
}
|
||||
|
||||
input[type='button']:hover {
|
||||
background: #040a0f;
|
||||
background: var(--button-hover);
|
||||
}
|
||||
|
||||
input[type='color'] {
|
||||
min-height: 2rem;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
label {
|
||||
vertical-align: middle;
|
||||
margin-bottom: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input:not([type='checkbox']):not([type='radio']),
|
||||
input[type='range'],
|
||||
select,
|
||||
button,
|
||||
textarea {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
margin-right: 0;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
textarea:not([cols]) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea:not([rows]) {
|
||||
min-height: 40px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
select {
|
||||
background: #161f27 url("data:image/svg+xml;charset=utf-8,%\
|
||||
3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version=\
|
||||
'1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w\
|
||||
3.org/1999/xlink' height='62.5' width='116.9' fill='%23efef\
|
||||
ef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L5\
|
||||
8.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.\
|
||||
4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60\
|
||||
.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,\
|
||||
1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat;
|
||||
background: var(--background) var(--select-arrow) calc(100% - \
|
||||
12px) 50% / 12px no-repeat;
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
padding-right: 10px;
|
||||
background-image: none;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
box-shadow: 0 0 0 2px #0096bfab;
|
||||
box-shadow: 0 0 0 2px var(--focus);
|
||||
}
|
||||
|
||||
select:focus {
|
||||
box-shadow: 0 0 0 2px #0096bfab;
|
||||
box-shadow: 0 0 0 2px var(--focus);
|
||||
}
|
||||
|
||||
button:focus {
|
||||
box-shadow: 0 0 0 2px #0096bfab;
|
||||
box-shadow: 0 0 0 2px var(--focus);
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
box-shadow: 0 0 0 2px #0096bfab;
|
||||
box-shadow: 0 0 0 2px var(--focus);
|
||||
}
|
||||
|
||||
input[type='checkbox']:active,
|
||||
input[type='radio']:active,
|
||||
input[type='submit']:active,
|
||||
input[type='reset']:active,
|
||||
input[type='button']:active,
|
||||
input[type='range']:active,
|
||||
button:active {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
input:disabled,
|
||||
select:disabled,
|
||||
button:disabled,
|
||||
textarea:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: #a9a9a9;
|
||||
color: var(--form-placeholder);
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: #a9a9a9;
|
||||
color: var(--form-placeholder);
|
||||
}
|
||||
|
||||
::-ms-input-placeholder {
|
||||
color: #a9a9a9;
|
||||
color: var(--form-placeholder);
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: #a9a9a9;
|
||||
color: var(--form-placeholder);
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px #0096bfab solid;
|
||||
border: 1px var(--focus) solid;
|
||||
border-radius: 6px;
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
margin: 10px 0;
|
||||
padding: 10px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
input[type='range']:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 9.5px;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
background: #526980;
|
||||
background: var(--border);
|
||||
-webkit-appearance: none;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
input[type='range']:focus::-webkit-slider-runnable-track {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 9.5px;
|
||||
-moz-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb {
|
||||
box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
background: #526980;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
input[type='range']::-ms-track {
|
||||
width: 100%;
|
||||
height: 9.5px;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
border-width: 16px 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
input[type='range']::-ms-fill-lower {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border: 0.2px solid #010101;
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
|
||||
}
|
||||
|
||||
input[type='range']::-ms-fill-upper {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border: 0.2px solid #010101;
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
|
||||
}
|
||||
|
||||
input[type='range']::-ms-thumb {
|
||||
box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
|
||||
border: 1px solid #000;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
background: #526980;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
input[type='range']:focus::-ms-fill-lower {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
input[type='range']:focus::-ms-fill-upper {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #41adff;
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
color: #ffbe85;
|
||||
color: var(--code);
|
||||
padding: 2.5px 5px;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
samp {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
color: #ffbe85;
|
||||
color: var(--code);
|
||||
padding: 2.5px 5px;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
time {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
color: #ffbe85;
|
||||
color: var(--code);
|
||||
padding: 2.5px 5px;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
var {
|
||||
color: #d941e2;
|
||||
color: var(--variable);
|
||||
font-style: normal;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border: 1px solid #526980;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
color: #dbdbdb;
|
||||
color: var(--text-main);
|
||||
padding: 2px 4px 2px 4px;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #526980;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table caption {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 6px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid #526980;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
tfoot {
|
||||
border-top: 1px solid #526980;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) button {
|
||||
background-color: #1a242f;
|
||||
background-color: var(--background-alt);
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) button:hover {
|
||||
background-color: #202b38;
|
||||
background-color: var(--background-body);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #161f27;
|
||||
background: var(--background);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #040a0f;
|
||||
background: var(--scrollbar-thumb);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(0, 0, 0);
|
||||
background: var(--scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: #1c76c5;
|
||||
background-color: var(--selection);
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #1c76c5;
|
||||
background-color: var(--selection);
|
||||
color: #fff;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
background-color: #1a242f;
|
||||
background-color: var(--background-alt);
|
||||
padding: 10px 10px 0;
|
||||
margin: 1em 0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
details[open] {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
details > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
details[open] summary {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
margin: -10px -10px 0;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
summary:hover,
|
||||
summary:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
details > :not(summary) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
summary::-webkit-details-marker {
|
||||
color: #dbdbdb;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
dialog {
|
||||
background-color: #1a242f;
|
||||
background-color: var(--background-alt);
|
||||
color: #dbdbdb;
|
||||
color: var(--text-main);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
border-color: #526980;
|
||||
border-color: var(--border);
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
dialog > header:first-child {
|
||||
background-color: #161f27;
|
||||
background-color: var(--background);
|
||||
border-radius: 6px 6px 0 0;
|
||||
margin: -10px -30px 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
dialog::-webkit-backdrop {
|
||||
background: #0000009c;
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: #0000009c;
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: 1px solid #526980;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 10px;
|
||||
color: #a9b1ba;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
body > footer {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body,
|
||||
pre,
|
||||
code,
|
||||
summary,
|
||||
details,
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
pre,
|
||||
code,
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
footer,
|
||||
summary,
|
||||
strong {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
summary::marker {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
summary::-webkit-details-marker {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
"""
|
||||
66
src/quart_imp/_cli/helpers.py
Normal file
66
src/quart_imp/_cli/helpers.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import re
|
||||
import typing as t
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def to_snake_case(string: str) -> str:
|
||||
"""
|
||||
Thank you openai
|
||||
"""
|
||||
# Replace any non-alphanumeric characters with underscores
|
||||
string = re.sub(r"[^a-zA-Z0-9]", "_", string)
|
||||
# Remove any consecutive underscores
|
||||
string = re.sub(r"_{2,}", "_", string)
|
||||
# Convert the string to lowercase
|
||||
string = string.lower()
|
||||
return string
|
||||
|
||||
|
||||
class Sprinkles:
|
||||
HEADER = "\033[95m"
|
||||
OKBLUE = "\033[94m"
|
||||
OKCYAN = "\033[96m"
|
||||
OKGREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
BOLD = "\033[1m"
|
||||
UNDERLINE = "\033[4m"
|
||||
END = "\033[0m"
|
||||
|
||||
|
||||
def build(
|
||||
folders: t.Dict[str, t.Any], files: t.Dict[str, t.Any], building: str = "App"
|
||||
) -> None:
|
||||
write_bytes: t.List[str] = [
|
||||
"resources/static/favicon.ico",
|
||||
"resources/static/img/quart-imp-logo.png",
|
||||
"static/img/quart-imp-logo.png",
|
||||
]
|
||||
|
||||
for folder, path in folders.items():
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True)
|
||||
click.echo(
|
||||
f"{Sprinkles.OKGREEN}{building} folder: {folder}, created{Sprinkles.END}"
|
||||
)
|
||||
else:
|
||||
click.echo(
|
||||
f"{Sprinkles.WARNING}{building} folder already exists: {folder}, skipping{Sprinkles.END}"
|
||||
)
|
||||
|
||||
for file, (path, content) in files.items():
|
||||
if not path.exists():
|
||||
if file in write_bytes:
|
||||
path.write_bytes(bytes.fromhex(content))
|
||||
continue
|
||||
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
click.echo(
|
||||
f"{Sprinkles.OKGREEN}{building} file: {file}, created{Sprinkles.END}"
|
||||
)
|
||||
else:
|
||||
click.echo(
|
||||
f"{Sprinkles.WARNING}{building} file already exists: {file}, skipping{Sprinkles.END}"
|
||||
)
|
||||
244
src/quart_imp/_cli/init.py
Normal file
244
src/quart_imp/_cli/init.py
Normal file
@@ -0,0 +1,244 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from .blueprint import add_blueprint
|
||||
from .filelib.favicon import favicon
|
||||
from .filelib.head_tag_generator import head_tag_generator
|
||||
from .filelib.quart_imp_logo import quart_imp_logo
|
||||
from .filelib.water_css import water_css
|
||||
from .helpers import Sprinkles as Sp
|
||||
from .helpers import build
|
||||
|
||||
|
||||
def minimal_app(app_folder: Path) -> None:
|
||||
from .filelib.init import init_minimal_py
|
||||
from .filelib.templates import templates_minimal_index_html
|
||||
from .filelib.resources import resources_minimal_routes_py
|
||||
|
||||
# Folders
|
||||
folders = {
|
||||
"root": app_folder,
|
||||
"resources": app_folder / "resources",
|
||||
"resources/static": app_folder / "resources" / "static",
|
||||
"resources/static/css": app_folder / "resources" / "static" / "css",
|
||||
"resources/static/img": app_folder / "resources" / "static" / "img",
|
||||
"resources/templates": app_folder / "resources" / "templates",
|
||||
}
|
||||
|
||||
files = {
|
||||
"root/__init__.py": (
|
||||
folders["root"] / "__init__.py",
|
||||
init_minimal_py(secret_key=os.urandom(24).hex()),
|
||||
),
|
||||
"resources/static/favicon.ico": (
|
||||
folders["resources/static"] / "favicon.ico",
|
||||
favicon,
|
||||
),
|
||||
"resources/static/css/main.css": (
|
||||
folders["resources/static/css"] / "water.css",
|
||||
water_css,
|
||||
),
|
||||
"resources/static/img/quart-imp-logo.png": (
|
||||
folders["resources/static/img"] / "quart-imp-logo.png",
|
||||
quart_imp_logo,
|
||||
),
|
||||
"resources/templates/index.html": (
|
||||
folders["resources/templates"] / "index.html",
|
||||
templates_minimal_index_html(
|
||||
head_tag=head_tag_generator(
|
||||
no_js=True,
|
||||
),
|
||||
static_path="static",
|
||||
index_py=str(folders["resources"] / "index.py"),
|
||||
index_html=str(folders["resources/templates"] / "index.html"),
|
||||
init_py=str(folders["root"] / "__init__.py"),
|
||||
),
|
||||
),
|
||||
"resources/routes.py": (
|
||||
folders["resources"] / "routes.py",
|
||||
resources_minimal_routes_py(),
|
||||
),
|
||||
}
|
||||
|
||||
build(folders, files)
|
||||
|
||||
|
||||
def slim_app(app_folder: Path) -> None:
|
||||
from .filelib.init import init_slim_py
|
||||
from .filelib.extensions import extensions_init
|
||||
from .filelib.resources import resources_cli_py
|
||||
from .filelib.resources import resources_error_handlers_py
|
||||
from .filelib.templates import templates_error_html
|
||||
|
||||
app_name = app_folder.name
|
||||
|
||||
folders = {
|
||||
"root": app_folder,
|
||||
"extensions": app_folder / "extensions",
|
||||
"resources": app_folder / "resources",
|
||||
"resources/cli": app_folder / "resources" / "cli",
|
||||
"resources/error_handlers": app_folder / "resources" / "error_handlers",
|
||||
"resources/static": app_folder / "resources" / "static",
|
||||
"resources/static/css": app_folder / "resources" / "static" / "css",
|
||||
"resources/static/img": app_folder / "resources" / "static" / "img",
|
||||
"resources/templates": app_folder / "resources" / "templates",
|
||||
}
|
||||
|
||||
files = {
|
||||
"root/__init__.py": (
|
||||
folders["root"] / "__init__.py",
|
||||
init_slim_py(app_name=app_name, secret_key=os.urandom(24).hex()),
|
||||
),
|
||||
"extensions/__init__.py": (
|
||||
folders["extensions"] / "__init__.py",
|
||||
extensions_init(),
|
||||
),
|
||||
"resources/cli/cli.py": (
|
||||
folders["resources/cli"] / "cli.py",
|
||||
resources_cli_py(),
|
||||
),
|
||||
"resources/error_handlers/error_handlers.py": (
|
||||
folders["resources/error_handlers"] / "error_handlers.py",
|
||||
resources_error_handlers_py(),
|
||||
),
|
||||
"resources/static/favicon.ico": (
|
||||
folders["resources/static"] / "favicon.ico",
|
||||
favicon,
|
||||
),
|
||||
"resources/templates/error.html": (
|
||||
folders["resources/templates"] / "error.html",
|
||||
templates_error_html(),
|
||||
),
|
||||
}
|
||||
|
||||
build(folders, files)
|
||||
|
||||
add_blueprint(
|
||||
name="www",
|
||||
_init_app=True,
|
||||
_cwd=app_folder,
|
||||
_url_prefix="/",
|
||||
)
|
||||
|
||||
|
||||
def full_app(app_folder: Path) -> None:
|
||||
from .filelib.init import init_full_py
|
||||
from .filelib.extensions import extensions_init
|
||||
from .filelib.resources import resources_cli_py
|
||||
from .filelib.resources import resources_error_handlers_py
|
||||
from .filelib.resources import resources_context_processors_py
|
||||
from .filelib.resources import resources_filters_py
|
||||
from .filelib.resources import resources_routes_py
|
||||
|
||||
from .filelib.templates import templates_error_html
|
||||
|
||||
app_name = app_folder.name
|
||||
|
||||
folders = {
|
||||
"root": app_folder,
|
||||
"blueprints": app_folder / "blueprints",
|
||||
"extensions": app_folder / "extensions",
|
||||
"resources": app_folder / "resources",
|
||||
"resources/cli": app_folder / "resources" / "cli",
|
||||
"resources/context_processors": app_folder / "resources" / "context_processors",
|
||||
"resources/error_handlers": app_folder / "resources" / "error_handlers",
|
||||
"resources/filters": app_folder / "resources" / "filters",
|
||||
"resources/routes": app_folder / "resources" / "routes",
|
||||
"resources/static": app_folder / "resources" / "static",
|
||||
"resources/static/css": app_folder / "resources" / "static" / "css",
|
||||
"resources/static/img": app_folder / "resources" / "static" / "img",
|
||||
"resources/templates": app_folder / "resources" / "templates",
|
||||
}
|
||||
|
||||
files = {
|
||||
"root/__init__.py": (
|
||||
folders["root"] / "__init__.py",
|
||||
init_full_py(app_name=app_name, secret_key=os.urandom(24).hex()),
|
||||
),
|
||||
"extensions/__init__.py": (
|
||||
folders["extensions"] / "__init__.py",
|
||||
extensions_init(),
|
||||
),
|
||||
"resources/cli/cli.py": (
|
||||
folders["resources/cli"] / "cli.py",
|
||||
resources_cli_py(),
|
||||
),
|
||||
"resources/context_processors/context_processors.py": (
|
||||
folders["resources/context_processors"] / "context_processors.py",
|
||||
resources_context_processors_py(),
|
||||
),
|
||||
"resources/error_handlers/error_handlers.py": (
|
||||
folders["resources/error_handlers"] / "error_handlers.py",
|
||||
resources_error_handlers_py(),
|
||||
),
|
||||
"resources/filters/filters.py": (
|
||||
folders["resources/filters"] / "filters.py",
|
||||
resources_filters_py(),
|
||||
),
|
||||
"resources/routes/routes.py": (
|
||||
folders["resources/routes"] / "routes.py",
|
||||
resources_routes_py(),
|
||||
),
|
||||
"resources/static/favicon.ico": (
|
||||
folders["resources/static"] / "favicon.ico",
|
||||
favicon,
|
||||
),
|
||||
"resources/templates/error.html": (
|
||||
folders["resources/templates"] / "error.html",
|
||||
templates_error_html(),
|
||||
),
|
||||
}
|
||||
|
||||
build(folders, files)
|
||||
|
||||
add_blueprint(
|
||||
name="www",
|
||||
folder="blueprints",
|
||||
_init_app=True,
|
||||
_cwd=app_folder,
|
||||
_url_prefix="/",
|
||||
)
|
||||
|
||||
|
||||
def init_app(
|
||||
name: str,
|
||||
_full: bool = False,
|
||||
_slim: bool = False,
|
||||
_minimal: bool = False,
|
||||
) -> None:
|
||||
click.echo(f"{Sp.OKGREEN}Creating App: {name}")
|
||||
|
||||
cwd = Path.cwd()
|
||||
|
||||
app_folder = cwd / name
|
||||
|
||||
if app_folder.exists():
|
||||
click.echo(f"{Sp.FAIL}{name} folder already exists!{Sp.END}")
|
||||
click.confirm("Are you sure you want to continue?", abort=True)
|
||||
|
||||
if _minimal:
|
||||
minimal_app(app_folder)
|
||||
elif _slim:
|
||||
slim_app(app_folder)
|
||||
elif _full:
|
||||
full_app(app_folder)
|
||||
else:
|
||||
click.echo(f"{Sp.FAIL}No app type selected!{Sp.END}")
|
||||
click.echo(f"{Sp.FAIL}Use --minimal, --slim, or --full{Sp.END}")
|
||||
return
|
||||
|
||||
click.echo(" ")
|
||||
click.echo(f"{Sp.OKBLUE}==================={Sp.END}")
|
||||
click.echo(f"{Sp.OKBLUE}Quart app deployed!{Sp.END}")
|
||||
click.echo(f"{Sp.OKBLUE}==================={Sp.END}")
|
||||
click.echo(" ")
|
||||
if name == "app":
|
||||
click.echo(f"{Sp.OKBLUE}Your app has the default name of 'app'{Sp.END}")
|
||||
click.echo(f"{Sp.OKBLUE}Quart will automatically look for this!{Sp.END}")
|
||||
click.echo(f"{Sp.OKBLUE}Run: quart run{Sp.END}")
|
||||
else:
|
||||
click.echo(f"{Sp.OKBLUE}Your app has the name of '{name}'{Sp.END}")
|
||||
click.echo(f"{Sp.OKBLUE}Run: quart --app {name} run{Sp.END}")
|
||||
click.echo(" ")
|
||||
29
src/quart_imp/auth/__init__.py
Normal file
29
src/quart_imp/auth/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from .__legacy__ import Auth
|
||||
from .authenticate_password import authenticate_password
|
||||
from .dataclasses import PasswordGeneration
|
||||
from .encrypt_password import encrypt_password
|
||||
from .generate_alphanumeric_validator import generate_alphanumeric_validator
|
||||
from .generate_csrf_token import generate_csrf_token
|
||||
from .generate_email_validator import generate_email_validator
|
||||
from .generate_numeric_validator import generate_numeric_validator
|
||||
from .generate_password import generate_password
|
||||
from .generate_private_key import generate_private_key
|
||||
from .generate_salt import generate_salt
|
||||
from .is_email_address_valid import is_email_address_valid
|
||||
from .is_username_valid import is_username_valid
|
||||
|
||||
__all__ = [
|
||||
"PasswordGeneration",
|
||||
"is_email_address_valid",
|
||||
"is_username_valid",
|
||||
"generate_csrf_token",
|
||||
"generate_private_key",
|
||||
"generate_numeric_validator",
|
||||
"generate_alphanumeric_validator",
|
||||
"generate_email_validator",
|
||||
"generate_salt",
|
||||
"encrypt_password",
|
||||
"authenticate_password",
|
||||
"generate_password",
|
||||
"Auth",
|
||||
]
|
||||
177
src/quart_imp/auth/__legacy__.py
Normal file
177
src/quart_imp/auth/__legacy__.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import typing as t
|
||||
from random import choice
|
||||
from string import ascii_letters
|
||||
|
||||
from .authenticate_password import authenticate_password
|
||||
from .encrypt_password import encrypt_password
|
||||
from .generate_alphanumeric_validator import generate_alphanumeric_validator
|
||||
from .generate_csrf_token import generate_csrf_token
|
||||
from .generate_email_validator import generate_email_validator
|
||||
from .generate_numeric_validator import generate_numeric_validator
|
||||
from .generate_password import generate_password
|
||||
from .generate_private_key import generate_private_key
|
||||
from .generate_salt import generate_salt
|
||||
from .is_email_address_valid import is_email_address_valid
|
||||
from .is_username_valid import is_username_valid
|
||||
|
||||
|
||||
def auth_password(
|
||||
input_password: str,
|
||||
database_password: str,
|
||||
database_salt: str,
|
||||
encrypt: int = 512,
|
||||
pepper_length: int = 1,
|
||||
) -> bool:
|
||||
"""Legacy method, use authenticate_password instead"""
|
||||
return authenticate_password(
|
||||
input_password, database_password, database_salt, encrypt, pepper_length
|
||||
)
|
||||
|
||||
|
||||
def hash_password(
|
||||
password: str, salt: str, encrypt: int = 512, pepper_length: int = 1
|
||||
) -> str:
|
||||
"""Legacy method, use encrypt_password instead"""
|
||||
return encrypt_password(password, salt, encrypt, pepper_length)
|
||||
|
||||
|
||||
def sha_password(
|
||||
password: str, salt: str, encrypt: int = 512, pepper_length: int = 1
|
||||
) -> str:
|
||||
"""Legacy method, use encrypt_password instead"""
|
||||
return hash_password(password, salt, encrypt, pepper_length)
|
||||
|
||||
|
||||
def generate_pepper(password: str, length: int = 1) -> str:
|
||||
"""Legacy method, stop using this"""
|
||||
return "".join(choice(ascii_letters) for _ in range(length)) + password
|
||||
|
||||
|
||||
def generate_form_token() -> str:
|
||||
"""Legacy method, use generate_csrf_token instead"""
|
||||
return generate_csrf_token()
|
||||
|
||||
|
||||
class Auth:
|
||||
@classmethod
|
||||
def is_email_address_valid(cls, email_address: str) -> bool:
|
||||
"""Legacy class method, use from quart_imp.auth import is_email_address_valid instead"""
|
||||
return is_email_address_valid(email_address)
|
||||
|
||||
@classmethod
|
||||
def is_username_valid(
|
||||
cls,
|
||||
username: str,
|
||||
allowed: t.Optional[t.List[t.Literal["all", "dot", "dash", "under"]]] = None,
|
||||
) -> bool:
|
||||
"""Legacy class method, use from quart_imp.auth import is_username_valid instead"""
|
||||
return is_username_valid(username, allowed)
|
||||
|
||||
@classmethod
|
||||
def generate_csrf_token(cls) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_csrf_token instead"""
|
||||
return generate_csrf_token()
|
||||
|
||||
@classmethod
|
||||
def generate_private_key(cls, hook: t.Optional[str]) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_private_key instead"""
|
||||
return generate_private_key(hook)
|
||||
|
||||
@classmethod
|
||||
def generate_numeric_validator(cls, length: int) -> int:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_numeric_validator instead"""
|
||||
return generate_numeric_validator(length)
|
||||
|
||||
@classmethod
|
||||
def generate_alphanumeric_validator(cls, length: int) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_alphanumeric_validator instead"""
|
||||
return generate_alphanumeric_validator(length)
|
||||
|
||||
@classmethod
|
||||
def generate_email_validator(cls) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_private_key instead"""
|
||||
return generate_email_validator()
|
||||
|
||||
@classmethod
|
||||
def generate_salt(cls, length: int = 4) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_salt instead"""
|
||||
return generate_salt(length)
|
||||
|
||||
@classmethod
|
||||
def encrypt_password(
|
||||
cls,
|
||||
password: str,
|
||||
salt: str,
|
||||
encryption_level: int = 512,
|
||||
pepper_length: int = 1,
|
||||
pepper_position: t.Literal["start", "end"] = "end",
|
||||
) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import encrypt_password instead"""
|
||||
return encrypt_password(
|
||||
password, salt, encryption_level, pepper_length, pepper_position
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def authenticate_password(
|
||||
cls,
|
||||
input_password: str,
|
||||
database_password: str,
|
||||
database_salt: str,
|
||||
encryption_level: int = 512,
|
||||
pepper_length: int = 1,
|
||||
pepper_position: t.Literal["start", "end"] = "end",
|
||||
) -> bool:
|
||||
"""Legacy class method, use from quart_imp.auth import authenticate_password instead"""
|
||||
return authenticate_password(
|
||||
input_password,
|
||||
database_password,
|
||||
database_salt,
|
||||
encryption_level,
|
||||
pepper_length,
|
||||
pepper_position,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_password(cls, style: str = "mixed", length: int = 3) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_password instead"""
|
||||
return generate_password(style, length)
|
||||
|
||||
# LEGACY METHODS
|
||||
|
||||
@classmethod
|
||||
def auth_password(
|
||||
cls,
|
||||
input_password: str,
|
||||
database_password: str,
|
||||
database_salt: str,
|
||||
encrypt: int = 512,
|
||||
pepper_length: int = 1,
|
||||
) -> bool:
|
||||
"""Legacy class method, use from quart_imp.auth import authenticate_password instead"""
|
||||
return authenticate_password(
|
||||
input_password, database_password, database_salt, encrypt, pepper_length
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def hash_password(
|
||||
cls, password: str, salt: str, encrypt: int = 512, pepper_length: int = 1
|
||||
) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import encrypt_password instead"""
|
||||
return encrypt_password(password, salt, encrypt, pepper_length)
|
||||
|
||||
@classmethod
|
||||
def sha_password(
|
||||
cls, password: str, salt: str, encrypt: int = 512, pepper_length: int = 1
|
||||
) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import encrypt_password instead"""
|
||||
return encrypt_password(password, salt, encrypt, pepper_length)
|
||||
|
||||
@classmethod
|
||||
def generate_pepper(cls, password: str, length: int = 1) -> str:
|
||||
"""Legacy class method, stop using this"""
|
||||
return "".join(choice(ascii_letters) for _ in range(length)) + password
|
||||
|
||||
@classmethod
|
||||
def generate_form_token(cls) -> str:
|
||||
"""Legacy class method, use from quart_imp.auth import generate_csrf_token instead"""
|
||||
return generate_csrf_token()
|
||||
33
src/quart_imp/auth/__private_funcs__.py
Normal file
33
src/quart_imp/auth/__private_funcs__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import typing as t
|
||||
from hashlib import sha256, sha512
|
||||
|
||||
|
||||
def _pps(pepper_: str, pass_: str, salt_: str) -> str:
|
||||
return pepper_ + pass_ + salt_
|
||||
|
||||
|
||||
def _ppe(pepper_: str, pass_: str, salt_: str) -> str:
|
||||
return pass_ + pepper_ + salt_
|
||||
|
||||
|
||||
def _guess_block(
|
||||
guesses: t.Set[str],
|
||||
input_password: str,
|
||||
database_password: str,
|
||||
database_salt: str,
|
||||
encryption_level: int = 512,
|
||||
pepper_position: t.Literal["start", "end"] = "end",
|
||||
) -> bool:
|
||||
for guess in guesses:
|
||||
_sha = sha512() if encryption_level == 512 else sha256()
|
||||
_sha.update(
|
||||
(
|
||||
_pps(guess, input_password, database_salt)
|
||||
if pepper_position == "start"
|
||||
else _ppe(guess, input_password, database_salt)
|
||||
).encode("utf-8")
|
||||
)
|
||||
if _sha.hexdigest() == database_password:
|
||||
return True
|
||||
|
||||
return False
|
||||
60
src/quart_imp/auth/authenticate_password.py
Normal file
60
src/quart_imp/auth/authenticate_password.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import typing as t
|
||||
from itertools import product
|
||||
from string import ascii_letters
|
||||
|
||||
from .__private_funcs__ import _guess_block
|
||||
|
||||
|
||||
def authenticate_password(
|
||||
input_password: str,
|
||||
database_password: str,
|
||||
database_salt: str,
|
||||
encryption_level: int = 512,
|
||||
pepper_length: int = 1,
|
||||
pepper_position: t.Literal["start", "end"] = "end",
|
||||
) -> bool:
|
||||
"""
|
||||
Takes the plain input password, the stored hashed password along with the stored salt
|
||||
and will try every possible combination of pepper values to find a match.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. Note::
|
||||
|
||||
You must know the length of the pepper used to hash the password.
|
||||
|
||||
You must know the position of the pepper used to hash the password.
|
||||
|
||||
You must know the encryption level used to hash the password.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param input_password: str - plain password
|
||||
:param database_password: str - hashed password from database
|
||||
:param database_salt: str - salt from database
|
||||
:param encryption_level: int - encryption used to generate database password
|
||||
:param pepper_length: int - length of pepper used to generate database password
|
||||
:param pepper_position: str - "start" or "end" - position of pepper used to generate database password
|
||||
:param use_multiprocessing: bool - use multiprocessing to speed up the process (not compatible with eventlet/gevent)
|
||||
:return: bool - True if match, False if not
|
||||
"""
|
||||
|
||||
if pepper_length > 3:
|
||||
pepper_length = 3
|
||||
|
||||
_guesses = {"".join(i) for i in product(ascii_letters, repeat=pepper_length)}
|
||||
|
||||
for guess in _guesses:
|
||||
if _guess_block(
|
||||
{guess},
|
||||
input_password,
|
||||
database_password,
|
||||
database_salt,
|
||||
encryption_level,
|
||||
pepper_position,
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
767
src/quart_imp/auth/dataclasses.py
Normal file
767
src/quart_imp/auth/dataclasses.py
Normal file
@@ -0,0 +1,767 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PasswordGeneration:
|
||||
"""
|
||||
This is a bank of words used to generate random passwords.
|
||||
"""
|
||||
|
||||
animals = [
|
||||
"Canidae",
|
||||
"Felidae",
|
||||
"Cat",
|
||||
"Cattle",
|
||||
"Dog",
|
||||
"Donkey",
|
||||
"Goat",
|
||||
"Horse",
|
||||
"Pig",
|
||||
"Rabbit",
|
||||
"Aardvark",
|
||||
"Aardwolf",
|
||||
"Albatross",
|
||||
"Alligator",
|
||||
"Alpaca",
|
||||
"Amphibian",
|
||||
"Anaconda",
|
||||
"Angelfish",
|
||||
"Anglerfish",
|
||||
"Ant",
|
||||
"Anteater",
|
||||
"Antelope",
|
||||
"Antlion",
|
||||
"Ape",
|
||||
"Aphid",
|
||||
"Armadillo",
|
||||
"Asp",
|
||||
"Baboon",
|
||||
"Badger",
|
||||
"Bandicoot",
|
||||
"Barnacle",
|
||||
"Barracuda",
|
||||
"Basilisk",
|
||||
"Bass",
|
||||
"Bat",
|
||||
"Bear",
|
||||
"Beaver",
|
||||
"Bedbug",
|
||||
"Bee",
|
||||
"Beetle",
|
||||
"Bird",
|
||||
"Bison",
|
||||
"Blackbird",
|
||||
"Boa",
|
||||
"Boar",
|
||||
"Bobcat",
|
||||
"Bobolink",
|
||||
"Bonobo",
|
||||
"Bovid",
|
||||
"Bug",
|
||||
"Butterfly",
|
||||
"Buzzard",
|
||||
"Camel",
|
||||
"Canid",
|
||||
"Capybara",
|
||||
"Cardinal",
|
||||
"Caribou",
|
||||
"Carp",
|
||||
"Cat",
|
||||
"Catshark",
|
||||
"Caterpillar",
|
||||
"Catfish",
|
||||
"Cattle",
|
||||
"Centipede",
|
||||
"Cephalopod",
|
||||
"Chameleon",
|
||||
"Cheetah",
|
||||
"Chickadee",
|
||||
"Chicken",
|
||||
"Chimpanzee",
|
||||
"Chinchilla",
|
||||
"Chipmunk",
|
||||
"Clam",
|
||||
"Clownfish",
|
||||
"Cobra",
|
||||
"Cockroach",
|
||||
"Cod",
|
||||
"Condor",
|
||||
"Constrictor",
|
||||
"Coral",
|
||||
"Cougar",
|
||||
"Cow",
|
||||
"Coyote",
|
||||
"Crab",
|
||||
"Crane",
|
||||
"Crawdad",
|
||||
"Crayfish",
|
||||
"Cricket",
|
||||
"Crocodile",
|
||||
"Crow",
|
||||
"Cuckoo",
|
||||
"Cicada",
|
||||
"Damselfly",
|
||||
"Deer",
|
||||
"Dingo",
|
||||
"Dinosaur",
|
||||
"Dog",
|
||||
"Dolphin",
|
||||
"Donkey",
|
||||
"Dormouse",
|
||||
"Dove",
|
||||
"Dragonfly",
|
||||
"Dragon",
|
||||
"Duck",
|
||||
"Eagle",
|
||||
"Earthworm",
|
||||
"Earwig",
|
||||
"Echidna",
|
||||
"Eel",
|
||||
"Egret",
|
||||
"Elephant",
|
||||
"Elk",
|
||||
"Emu",
|
||||
"Ermine",
|
||||
"Falcon",
|
||||
"Ferret",
|
||||
"Finch",
|
||||
"Firefly",
|
||||
"Fish",
|
||||
"Flamingo",
|
||||
"Flea",
|
||||
"Fly",
|
||||
"Flyingfish",
|
||||
"Fowl",
|
||||
"Fox",
|
||||
"Frog",
|
||||
"Gamefowl",
|
||||
"Galliform",
|
||||
"Gazelle",
|
||||
"Gecko",
|
||||
"Gerbil",
|
||||
"Gibbon",
|
||||
"Giraffe",
|
||||
"Goat",
|
||||
"Goldfish",
|
||||
"Goose",
|
||||
"Gopher",
|
||||
"Gorilla",
|
||||
"Grasshopper",
|
||||
"Grouse",
|
||||
"Guan",
|
||||
"Guanaco",
|
||||
"Guineafowl",
|
||||
"Gull",
|
||||
"Guppy",
|
||||
"Haddock",
|
||||
"Halibut",
|
||||
"Hamster",
|
||||
"Hare",
|
||||
"Harrier",
|
||||
"Hawk",
|
||||
"Hedgehog",
|
||||
"Heron",
|
||||
"Herring",
|
||||
"Hippopotamus",
|
||||
"Hookworm",
|
||||
"Hornet",
|
||||
"Horse",
|
||||
"Hoverfly",
|
||||
"Hummingbird",
|
||||
"Hyena",
|
||||
"Iguana",
|
||||
"Impala",
|
||||
"Jackal",
|
||||
"Jaguar",
|
||||
"Jay",
|
||||
"Jellyfish",
|
||||
"Junglefowl",
|
||||
"Kangaroo",
|
||||
"Kingfisher",
|
||||
"Kite",
|
||||
"Kiwi",
|
||||
"Koala",
|
||||
"Koi",
|
||||
"Krill",
|
||||
"Ladybug",
|
||||
"Lamprey",
|
||||
"Landfowl",
|
||||
"Lark",
|
||||
"Leech",
|
||||
"Lemming",
|
||||
"Lemur",
|
||||
"Leopard",
|
||||
"Leopon",
|
||||
"Limpet",
|
||||
"Lion",
|
||||
"Lizard",
|
||||
"Llama",
|
||||
"Lobster",
|
||||
"Locust",
|
||||
"Loon",
|
||||
"Louse",
|
||||
"Lungfish",
|
||||
"Lynx",
|
||||
"Macaw",
|
||||
"Mackerel",
|
||||
"Magpie",
|
||||
"Mammal",
|
||||
"Manatee",
|
||||
"Mandrill",
|
||||
"Marlin",
|
||||
"Marmoset",
|
||||
"Marmot",
|
||||
"Marsupial",
|
||||
"Marten",
|
||||
"Mastodon",
|
||||
"Meadowlark",
|
||||
"Meerkat",
|
||||
"Mink",
|
||||
"Minnow",
|
||||
"Mite",
|
||||
"Mockingbird",
|
||||
"Mole",
|
||||
"Mollusk",
|
||||
"Mongoose",
|
||||
"Monkey",
|
||||
"Moose",
|
||||
"Mosquito",
|
||||
"Moth",
|
||||
"Mouse",
|
||||
"Mule",
|
||||
"Muskox",
|
||||
"Narwhal",
|
||||
"Newt",
|
||||
"Nightingale",
|
||||
"Ocelot",
|
||||
"Octopus",
|
||||
"Opossum",
|
||||
"Orangutan",
|
||||
"Orca",
|
||||
"Ostrich",
|
||||
"Otter",
|
||||
"Owl",
|
||||
"Ox",
|
||||
"Panda",
|
||||
"Panther",
|
||||
"Parakeet",
|
||||
"Parrot",
|
||||
"Parrotfish",
|
||||
"Partridge",
|
||||
"Peacock",
|
||||
"Peafowl",
|
||||
"Pelican",
|
||||
"Penguin",
|
||||
"Perch",
|
||||
"Pheasant",
|
||||
"Pig",
|
||||
"Pigeon",
|
||||
"Pike",
|
||||
"Pinniped",
|
||||
"Piranha",
|
||||
"Planarian",
|
||||
"Platypus",
|
||||
"Pony",
|
||||
"Porcupine",
|
||||
"Porpoise",
|
||||
"Possum",
|
||||
"Prawn",
|
||||
"Primate",
|
||||
"Ptarmigan",
|
||||
"Puffin",
|
||||
"Puma",
|
||||
"Python",
|
||||
"Quail",
|
||||
"Quelea",
|
||||
"Quokka",
|
||||
"Rabbit",
|
||||
"Raccoon",
|
||||
"Rat",
|
||||
"Rattlesnake",
|
||||
"Raven",
|
||||
"Reindeer",
|
||||
"Reptile",
|
||||
"Rhinoceros",
|
||||
"Roadrunner",
|
||||
"Rodent",
|
||||
"Rook",
|
||||
"Rooster",
|
||||
"Roundworm",
|
||||
"Sailfish",
|
||||
"Salamander",
|
||||
"Salmon",
|
||||
"Sawfish",
|
||||
"Scallop",
|
||||
"Scorpion",
|
||||
"Seahorse",
|
||||
"Shark",
|
||||
"Sheep",
|
||||
"Shrew",
|
||||
"Shrimp",
|
||||
"Silkworm",
|
||||
"Silverfish",
|
||||
"Skink",
|
||||
"Skunk",
|
||||
"Sloth",
|
||||
"Slug",
|
||||
"Smelt",
|
||||
"Snail",
|
||||
"Snake",
|
||||
"Snipe",
|
||||
"Sole",
|
||||
"Sparrow",
|
||||
"Spider",
|
||||
"Spoonbill",
|
||||
"Squid",
|
||||
"Squirrel",
|
||||
"Starfish",
|
||||
"Stingray",
|
||||
"Stoat",
|
||||
"Stork",
|
||||
"Sturgeon",
|
||||
"Swallow",
|
||||
"Swan",
|
||||
"Swift",
|
||||
"Swordfish",
|
||||
"Swordtail",
|
||||
"Tahr",
|
||||
"Takin",
|
||||
"Tapir",
|
||||
"Tarantula",
|
||||
"Tarsier",
|
||||
"Termite",
|
||||
"Tern",
|
||||
"Thrush",
|
||||
"Tick",
|
||||
"Tiger",
|
||||
"Tiglon",
|
||||
"Toad",
|
||||
"Tortoise",
|
||||
"Toucan",
|
||||
"Trout",
|
||||
"Tuna",
|
||||
"Turkey",
|
||||
"Turtle",
|
||||
"Tyrannosaurus",
|
||||
"Urial",
|
||||
"Vicuna",
|
||||
"Viper",
|
||||
"Vole",
|
||||
"Vulture",
|
||||
"Wallaby",
|
||||
"Walrus",
|
||||
"Wasp",
|
||||
"Warbler",
|
||||
"Weasel",
|
||||
"Whale",
|
||||
"Whippet",
|
||||
"Whitefish",
|
||||
"Wildcat",
|
||||
"Wildebeest",
|
||||
"Wildfowl",
|
||||
"Wolf",
|
||||
"Wolverine",
|
||||
"Wombat",
|
||||
"Woodpecker",
|
||||
"Worm",
|
||||
"Wren",
|
||||
"Xerinae",
|
||||
"Yak",
|
||||
"Zebra",
|
||||
"Alpaca",
|
||||
"Cat",
|
||||
"Cattle",
|
||||
"Chicken",
|
||||
"Dog",
|
||||
"Donkey",
|
||||
"Ferret",
|
||||
"Gayal",
|
||||
"Goldfish",
|
||||
"Guppy",
|
||||
"Horse",
|
||||
"Koi",
|
||||
"Llama",
|
||||
"Sheep",
|
||||
"Yak",
|
||||
]
|
||||
|
||||
colors = [
|
||||
"DarkViolet",
|
||||
"MediumVioletRed",
|
||||
"Rose",
|
||||
"Avocado",
|
||||
"Greenish",
|
||||
"Blood",
|
||||
"Sangria",
|
||||
"Pastel",
|
||||
"Night",
|
||||
"Celeste",
|
||||
"Ocean",
|
||||
"Cloudy",
|
||||
"Battleship",
|
||||
"Oak",
|
||||
"BlanchedAlmond",
|
||||
"Gold",
|
||||
"Slate",
|
||||
"DarkGray",
|
||||
"MidnightBlue",
|
||||
"PeachPuff",
|
||||
"Dark",
|
||||
"Chartreuse",
|
||||
"Bashful",
|
||||
"PaleVioletRed",
|
||||
"DarkTurquoise",
|
||||
"Grapefruit",
|
||||
"Sun",
|
||||
"Eggplant",
|
||||
"Golden",
|
||||
"Cyan",
|
||||
"Sand",
|
||||
"LightYellow",
|
||||
"Cobalt",
|
||||
"Tron",
|
||||
"Ruby",
|
||||
"Mustard",
|
||||
"AntiqueWhite",
|
||||
"Western",
|
||||
"Deep-Sea",
|
||||
"Iron",
|
||||
"LimeGreen",
|
||||
"Orange",
|
||||
"DarkCyan",
|
||||
"Velvet",
|
||||
"Clover",
|
||||
"Butterfly",
|
||||
"Jasmine",
|
||||
"Fire",
|
||||
"DarkSlateGray",
|
||||
"Heliotrope",
|
||||
"Scarlet",
|
||||
"Medium",
|
||||
"Unbleached",
|
||||
"Dimorphotheca",
|
||||
"Cornsilk",
|
||||
"GoldenRod",
|
||||
"Beer",
|
||||
"Canary",
|
||||
"DeepPink",
|
||||
"Sunrise",
|
||||
"SlateGray",
|
||||
"Burnt",
|
||||
"Algae",
|
||||
"Granite",
|
||||
"Baby",
|
||||
"Cream",
|
||||
"LightBlue",
|
||||
"Tan",
|
||||
"Yellow",
|
||||
"Burgundy",
|
||||
"Cherry",
|
||||
"Papaya",
|
||||
"Lapis",
|
||||
"Robin",
|
||||
"Mango",
|
||||
"Blush",
|
||||
"Blueberry",
|
||||
"Roman",
|
||||
"Bisque",
|
||||
"Iceberg",
|
||||
"Rosy",
|
||||
"Teal",
|
||||
"SeaShell",
|
||||
"Copper",
|
||||
"Pea",
|
||||
"Jeans",
|
||||
"Watermelon",
|
||||
"Grayish",
|
||||
"Flamingo",
|
||||
"Rich",
|
||||
"Navy",
|
||||
"Raspberry",
|
||||
"Lime",
|
||||
"Halloween",
|
||||
"RosyBrown",
|
||||
"Tangerine",
|
||||
"Sea",
|
||||
"Wood",
|
||||
"MediumOrchid",
|
||||
"Shamrock",
|
||||
"Chameleon",
|
||||
"Glacial",
|
||||
"BlueViolet",
|
||||
"Deep",
|
||||
"FloralWhite",
|
||||
"Fall",
|
||||
"Black",
|
||||
"Marble",
|
||||
"Hazel",
|
||||
"Hot",
|
||||
"DarkSalmon",
|
||||
"LavenderBlush",
|
||||
"Organic",
|
||||
"Violet",
|
||||
"MintCream",
|
||||
"Slime",
|
||||
"DarkSlateBlue",
|
||||
"DodgerBlue",
|
||||
"MediumSpringGreen",
|
||||
"Bee",
|
||||
"Jade",
|
||||
"Sage",
|
||||
"Egg",
|
||||
"Neon",
|
||||
"WhiteSmoke",
|
||||
"Grape",
|
||||
"LightCyan",
|
||||
"Acid",
|
||||
"Day",
|
||||
"Earth",
|
||||
"Olive",
|
||||
"Balloon",
|
||||
"Pine",
|
||||
"Rice",
|
||||
"OliveDrab",
|
||||
"Tulip",
|
||||
"Corn",
|
||||
"Rosy-Finch",
|
||||
"Dirty",
|
||||
"Coffee",
|
||||
"Vampire",
|
||||
"Pig",
|
||||
"Jellyfish",
|
||||
"Salmon",
|
||||
"Vermilion",
|
||||
"Camouflage",
|
||||
"IndianRed",
|
||||
"Mint",
|
||||
"Viola",
|
||||
"Venom",
|
||||
"Cookie",
|
||||
"HoneyDew",
|
||||
"MediumSeaGreen",
|
||||
"DarkMagenta",
|
||||
"Magic",
|
||||
"DarkGoldenRod",
|
||||
"Lipstick",
|
||||
"Tomato",
|
||||
"Lavender",
|
||||
"LightSkyBlue",
|
||||
"Midday",
|
||||
"Seafoam",
|
||||
"CornflowerBlue",
|
||||
"GhostWhite",
|
||||
"Carbon",
|
||||
"PapayaWhip",
|
||||
"Wheat",
|
||||
"Harvest",
|
||||
"SteelBlue",
|
||||
"Gulf",
|
||||
"Mauve",
|
||||
"Champagne",
|
||||
"DarkOliveGreen",
|
||||
"PaleGoldenRod",
|
||||
"Oil",
|
||||
"Clematis",
|
||||
"Deer",
|
||||
"Purple",
|
||||
"LightGray",
|
||||
"Parchment",
|
||||
"PaleTurquoise",
|
||||
"Northern",
|
||||
"MistyRose",
|
||||
"Tea",
|
||||
"Ginger",
|
||||
"New",
|
||||
"AliceBlue",
|
||||
"Jungle",
|
||||
"SlateBlue",
|
||||
"Khaki",
|
||||
"RebeccaPurple",
|
||||
"Pale",
|
||||
"Water",
|
||||
"School",
|
||||
"Sepia",
|
||||
"Wisteria",
|
||||
"LightPink",
|
||||
"Stoplight",
|
||||
"Seaweed",
|
||||
"DimGray",
|
||||
"Mocha",
|
||||
"LightGoldenRodYellow",
|
||||
"Donut",
|
||||
"Basket",
|
||||
"Dusty",
|
||||
"Construction",
|
||||
"Metallic",
|
||||
"Chestnut",
|
||||
"Light",
|
||||
"Fuchsia",
|
||||
"SeaGreen",
|
||||
"Plum",
|
||||
"RoyalBlue",
|
||||
"BurlyWood",
|
||||
"Azure",
|
||||
"Very",
|
||||
"Aztech",
|
||||
"Gray",
|
||||
"DarkSeaGreen",
|
||||
"LemonChiffon",
|
||||
"FireBrick",
|
||||
"Dull",
|
||||
"Brown",
|
||||
"Ash",
|
||||
"Denim",
|
||||
"Dull-Sea",
|
||||
"Sapphire",
|
||||
"Carnation",
|
||||
"Antique",
|
||||
"Dragon",
|
||||
"PaleGreen",
|
||||
"LightSteelBlue",
|
||||
"Cinnamon",
|
||||
"Heavenly",
|
||||
"Sonic",
|
||||
"Coral",
|
||||
"SkyBlue",
|
||||
"Jet",
|
||||
"Thistle",
|
||||
"Beetle",
|
||||
"Blonde",
|
||||
"Red",
|
||||
"MediumSlateBlue",
|
||||
"HotPink",
|
||||
"MediumBlue",
|
||||
"DarkKhaki",
|
||||
"Carrot",
|
||||
"DeepSkyBlue",
|
||||
"Taupe",
|
||||
"Aquamarine",
|
||||
"Pistachio",
|
||||
"DarkOrange",
|
||||
"Camel",
|
||||
"Gainsboro",
|
||||
"DarkRed",
|
||||
"Linen",
|
||||
"Kelly",
|
||||
"Off",
|
||||
"Macaw",
|
||||
"Bullet",
|
||||
"MediumPurple",
|
||||
"Brass",
|
||||
"Cardboard",
|
||||
"Sienna",
|
||||
"Midnight",
|
||||
"Electric",
|
||||
"CadetBlue",
|
||||
"Chilli",
|
||||
"Columbia",
|
||||
"Vanilla",
|
||||
"Puce",
|
||||
"Snow",
|
||||
"Bean",
|
||||
"Cadillac",
|
||||
"LightCoral",
|
||||
"Soft",
|
||||
"MediumTurquoise",
|
||||
"Bold",
|
||||
"NavajoWhite",
|
||||
"Cantaloupe",
|
||||
"Blue",
|
||||
"Maroon",
|
||||
"LightSlateGray",
|
||||
"Cotton",
|
||||
"Iguana",
|
||||
"Chrome",
|
||||
"DarkOrchid",
|
||||
"Indigo",
|
||||
"Moccasin",
|
||||
"Orchid",
|
||||
"Nebula",
|
||||
"Milk",
|
||||
"Fern",
|
||||
"GreenYellow",
|
||||
"Ferrari",
|
||||
"Pearl",
|
||||
"Bakers",
|
||||
"Bright",
|
||||
"Emerald",
|
||||
"Beige",
|
||||
"Army",
|
||||
"Alien",
|
||||
"Periwinkle",
|
||||
"SpringGreen",
|
||||
"Rubber",
|
||||
"Chocolate",
|
||||
"Charcoal",
|
||||
"Tiger",
|
||||
"Nardo",
|
||||
"Rogue",
|
||||
"Aqua",
|
||||
"Lilac",
|
||||
"PowderBlue",
|
||||
"OrangeRed",
|
||||
"SaddleBrown",
|
||||
"DarkBlue",
|
||||
"Hummingbird",
|
||||
"White",
|
||||
"Saffron",
|
||||
"Old",
|
||||
"LightSalmon",
|
||||
"LightSeaGreen",
|
||||
"OldLace",
|
||||
"Cranberry",
|
||||
"Zombie",
|
||||
"Crocus",
|
||||
"Windows",
|
||||
"Frog",
|
||||
"Peru",
|
||||
"DarkGreen",
|
||||
"Ivory",
|
||||
"Love",
|
||||
"Pink",
|
||||
"Sky",
|
||||
"Mahogany",
|
||||
"French",
|
||||
"SandyBrown",
|
||||
"Dollar",
|
||||
"Dinosaur",
|
||||
"Sedona",
|
||||
"ForestGreen",
|
||||
"Mist",
|
||||
"Smokey",
|
||||
"Crystal",
|
||||
"Iridium",
|
||||
"Banana",
|
||||
"Desert",
|
||||
"LightGreen",
|
||||
"Sandstone",
|
||||
"Silver",
|
||||
"Valentine",
|
||||
"Silk",
|
||||
"Green",
|
||||
"Parrot",
|
||||
"Macaroni",
|
||||
"Caramel",
|
||||
"Pumpkin",
|
||||
"Indian",
|
||||
"Crimson",
|
||||
"Tiffany",
|
||||
"Gunmetal",
|
||||
"Salad",
|
||||
"Platinum",
|
||||
"MediumAquaMarine",
|
||||
"Bronze",
|
||||
"Lava",
|
||||
"Peach",
|
||||
"Tyrian",
|
||||
"Rust",
|
||||
"Petra",
|
||||
"Lovely",
|
||||
"Aloe",
|
||||
"Blossom",
|
||||
"Rat",
|
||||
"Shocking",
|
||||
"LawnGreen",
|
||||
"YellowGreen",
|
||||
"Turquoise",
|
||||
]
|
||||
66
src/quart_imp/auth/encrypt_password.py
Normal file
66
src/quart_imp/auth/encrypt_password.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import typing as t
|
||||
from hashlib import sha256, sha512
|
||||
from random import choice
|
||||
from string import ascii_letters
|
||||
|
||||
from .__private_funcs__ import _pps, _ppe
|
||||
|
||||
|
||||
def encrypt_password(
|
||||
password: str,
|
||||
salt: str,
|
||||
encryption_level: int = 512,
|
||||
pepper_length: int = 1,
|
||||
pepper_position: t.Literal["start", "end"] = "end",
|
||||
) -> str:
|
||||
"""
|
||||
Takes the plain password, applies a pepper, salts it, then produces a digested sha512 or sha256 if specified.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
Can set the encryption level to 256 or 512, defaults to 512.
|
||||
|
||||
Can set the pepper length, defaults to 1. Max is 3.
|
||||
|
||||
Can set the pepper position, "start" or "end", defaults to "end".
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
For use in password hashing.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. Note::
|
||||
|
||||
You must inform the authenticate_password function of the pepper length used to hash the password.
|
||||
|
||||
You must inform the authenticate_password function of the position of the pepper used to hash the password.
|
||||
|
||||
You must inform the authenticate_password function of the encryption level used to hash the password.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param password: str - plain password
|
||||
:param salt: str - salt
|
||||
:param encryption_level: int - 256 or 512 - defaults to 512
|
||||
:param pepper_length: int - length of pepper
|
||||
:param pepper_position: str - "start" or "end" - defaults to "end"
|
||||
:return str: hash:
|
||||
"""
|
||||
|
||||
if pepper_length > 3:
|
||||
pepper_length = 3
|
||||
|
||||
_sha = sha512() if encryption_level == 512 else sha256()
|
||||
_pepper = "".join(choice(ascii_letters) for _ in range(pepper_length))
|
||||
|
||||
_sha.update(
|
||||
(
|
||||
_pps(_pepper, password, salt)
|
||||
if pepper_position == "start"
|
||||
else _ppe(_pepper, password, salt)
|
||||
).encode("utf-8")
|
||||
)
|
||||
return _sha.hexdigest()
|
||||
21
src/quart_imp/auth/generate_alphanumeric_validator.py
Normal file
21
src/quart_imp/auth/generate_alphanumeric_validator.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from random import choice
|
||||
from string import ascii_uppercase, digits
|
||||
|
||||
|
||||
def generate_alphanumeric_validator(length: int) -> str:
|
||||
"""
|
||||
Generates (length) of alphanumeric.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
For use in MFA email, or unique filename generation.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
:param length: int - length of alphanumeric to generate
|
||||
:return: str - Example return of "F5R6" if length is 4
|
||||
"""
|
||||
|
||||
_alpha_numeric = ascii_uppercase + digits
|
||||
return "".join([choice(_alpha_numeric) for _ in range(length)])
|
||||
21
src/quart_imp/auth/generate_csrf_token.py
Normal file
21
src/quart_imp/auth/generate_csrf_token.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from datetime import datetime
|
||||
from hashlib import sha1
|
||||
|
||||
|
||||
def generate_csrf_token() -> str:
|
||||
"""
|
||||
Generates a SHA1 using the current date and time.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
For use in Cross-Site Request Forgery.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:return: str - sha1
|
||||
"""
|
||||
sha = sha1()
|
||||
sha.update(str(datetime.now()).encode("utf-8"))
|
||||
return sha.hexdigest()
|
||||
20
src/quart_imp/auth/generate_email_validator.py
Normal file
20
src/quart_imp/auth/generate_email_validator.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from .generate_alphanumeric_validator import generate_alphanumeric_validator
|
||||
|
||||
|
||||
def generate_email_validator() -> str:
|
||||
"""
|
||||
Uses generate_alphanumeric_validator with a length of 8 to
|
||||
generate a random alphanumeric value for the specific use of
|
||||
validating accounts via email.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
See `generate_alphanumeric_validator` for more information.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:return: str - alphanumeric of length 8
|
||||
"""
|
||||
return str(generate_alphanumeric_validator(length=8))
|
||||
24
src/quart_imp/auth/generate_numeric_validator.py
Normal file
24
src/quart_imp/auth/generate_numeric_validator.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from random import randrange
|
||||
|
||||
|
||||
def generate_numeric_validator(length: int) -> int:
|
||||
"""
|
||||
Generates random choice between 1 * (length) and 9 * (length).
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
If the length is 4, it will generate a number between 1111 and 9999.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
For use in MFA email, or unique filename generation.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
:param length: int - length of number to generate
|
||||
:return: int - Example return of number between 1111 and 9999 if length is 4
|
||||
"""
|
||||
start = int("1" * length)
|
||||
end = int("9" * length)
|
||||
return randrange(start, end)
|
||||
56
src/quart_imp/auth/generate_password.py
Normal file
56
src/quart_imp/auth/generate_password.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from random import choice
|
||||
|
||||
from .dataclasses import PasswordGeneration
|
||||
from .generate_numeric_validator import generate_numeric_validator
|
||||
|
||||
|
||||
def generate_password(style: str = "mixed", length: int = 3) -> str:
|
||||
"""
|
||||
Generates a plain text password based on choice of style and length.
|
||||
2 random numbers are appended to the end of every generated password.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
style options: "animals", "colors", "mixed" - defaults to "mixed"
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example use:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
generate_password(style="animals", length=3)
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Output:**
|
||||
|
||||
Cat-Goat-Pig12
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param style: str - "animals", "colors", "mixed" - defaults to "mixed"
|
||||
:param length: int - how many words are chosen - defaults to 3
|
||||
:return: str - a generated plain text password
|
||||
"""
|
||||
if style == "animals":
|
||||
return "-".join(
|
||||
[choice(PasswordGeneration.animals) for _ in range(length)]
|
||||
) + str(generate_numeric_validator(length=2))
|
||||
|
||||
if style == "colors":
|
||||
return "-".join(
|
||||
[choice(PasswordGeneration.colors) for _ in range(length)]
|
||||
) + str(generate_numeric_validator(length=2))
|
||||
|
||||
if style == "mixed":
|
||||
return "-".join(
|
||||
[
|
||||
choice([*PasswordGeneration.animals, *PasswordGeneration.colors])
|
||||
for _ in range(length)
|
||||
]
|
||||
) + str(generate_numeric_validator(length=2))
|
||||
|
||||
raise ValueError(f"Invalid style passed in {style}")
|
||||
30
src/quart_imp/auth/generate_private_key.py
Normal file
30
src/quart_imp/auth/generate_private_key.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import typing as t
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
from random import randrange
|
||||
|
||||
|
||||
def generate_private_key(hook: t.Optional[str]) -> str:
|
||||
"""
|
||||
Generates a sha256 private key from a passed in hook value.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
If no hook is passed in, it will generate a hook using datetime.now() and a
|
||||
random number between 1 and 1000.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param hook: str - hook value to generate private key from
|
||||
:return: str - sha256
|
||||
"""
|
||||
|
||||
if hook is None:
|
||||
_range = randrange(1, 1000)
|
||||
hook = f"{datetime.now()}-{_range}"
|
||||
|
||||
sha = sha256()
|
||||
sha.update(hook.encode("utf-8"))
|
||||
return sha.hexdigest()
|
||||
23
src/quart_imp/auth/generate_salt.py
Normal file
23
src/quart_imp/auth/generate_salt.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from random import choice
|
||||
from string import punctuation
|
||||
|
||||
|
||||
def generate_salt(length: int = 4) -> str:
|
||||
"""
|
||||
Generates a string of (length) characters of punctuation.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
The Default length is 4.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
For use in password salting
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:return: str - salt of (length)
|
||||
"""
|
||||
return "".join(choice(punctuation) for _ in range(length))
|
||||
41
src/quart_imp/auth/is_email_address_valid.py
Normal file
41
src/quart_imp/auth/is_email_address_valid.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import re
|
||||
|
||||
|
||||
def is_email_address_valid(email_address: str) -> bool:
|
||||
"""
|
||||
Checks if email_address is a valid email address.
|
||||
|
||||
Is not completely RFC 5322 compliant, but it is good enough for most use cases.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
Here are examples of mistakes that it will not catch:
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
Valid but fails:
|
||||
|
||||
- email@[123.123.123.123] is VALID => PASSED : False
|
||||
- “email”@example.com is VALID => PASSED : False
|
||||
- very.unusual.“@”.unusual.com@example.com is VALID => PASSED : False
|
||||
- very.“(),:;<>[]”.VERY.“very@\\ "very”.unusual@strange.example.com is VALID => PASSED : False
|
||||
|
||||
Invalid but passes:
|
||||
|
||||
- email@example.com (Joe Smith) is INVALID => PASSED : True
|
||||
- email@111.222.333.44444 is INVALID => PASSED : True
|
||||
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param email_address: str
|
||||
:return: bool
|
||||
"""
|
||||
pattern = re.compile(
|
||||
r"[a-z\d!#$%&'*+?^_`{|}~-]+(?:\.[a-z\d!#$%&'*+?^_`"
|
||||
r"{|}~-]+)*@(?:[a-z\d](?:[a-z\d-]*[a-z\d])?\.)+[a-z\d](?:[a-z\d-]*[a-z\d])?",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
return bool(pattern.match(email_address))
|
||||
92
src/quart_imp/auth/is_username_valid.py
Normal file
92
src/quart_imp/auth/is_username_valid.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import re
|
||||
import typing as t
|
||||
|
||||
|
||||
def is_username_valid(
|
||||
username: str,
|
||||
allowed: t.Optional[t.List[t.Literal["all", "dot", "dash", "under"]]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Checks if a username is valid.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
Valid usernames can only include letters,
|
||||
numbers, ., -, and _ but cannot begin or end with
|
||||
the last three mentioned.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
|
||||
**Example use:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
is_username_valid("username", allowed=["all"])
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Output:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
username : WILL PASS : True
|
||||
user.name : WILL PASS : True
|
||||
user-name : WILL PASS : True
|
||||
user_name : WILL PASS : True
|
||||
_user_name : WILL PASS : False
|
||||
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
is_username_valid("username", allowed=["dot", "dash"])
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Output:**
|
||||
|
||||
.. code-block::
|
||||
|
||||
username : WILL PASS : True
|
||||
user.name : WILL PASS : True
|
||||
user-name : WILL PASS : True
|
||||
user-name.name : WILL PASS : True
|
||||
user_name : WILL PASS : False
|
||||
_user_name : WILL PASS : False
|
||||
.user.name : WILL PASS : False
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param username: str
|
||||
:param allowed: list - ["all", "dot", "dash", "under"] - defaults to ["all"]
|
||||
:return bool:
|
||||
"""
|
||||
|
||||
if not username[0].isalnum() or not username[-1].isalnum():
|
||||
return False
|
||||
|
||||
if allowed is None:
|
||||
allowed = ["all"]
|
||||
|
||||
if "all" in allowed:
|
||||
return bool(re.match(r"^[a-zA-Z0-9._-]+$", username))
|
||||
|
||||
if "under" not in allowed:
|
||||
if "_" in username:
|
||||
return False
|
||||
|
||||
if "dot" not in allowed:
|
||||
if "." in username:
|
||||
return False
|
||||
|
||||
if "dash" not in allowed:
|
||||
if "-" in username:
|
||||
return False
|
||||
|
||||
return True
|
||||
9
src/quart_imp/config/__init__.py
Normal file
9
src/quart_imp/config/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .imp_blueprint_config import ImpBlueprintConfig
|
||||
from .imp_config import ImpConfig
|
||||
from .quart_config import QuartConfig
|
||||
|
||||
__all__ = [
|
||||
"ImpConfig",
|
||||
"ImpBlueprintConfig",
|
||||
"QuartConfig",
|
||||
]
|
||||
55
src/quart_imp/config/imp_blueprint_config.py
Normal file
55
src/quart_imp/config/imp_blueprint_config.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import typing as t
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ImpBlueprintConfig:
|
||||
enabled: t.Optional[bool] = None
|
||||
url_prefix: t.Optional[str] = None
|
||||
subdomain: t.Optional[str] = None
|
||||
url_default: t.Optional[t.Dict[str, t.Any]] = None
|
||||
static_folder: t.Optional[str] = None
|
||||
template_folder: t.Optional[str] = None
|
||||
static_url_path: t.Optional[str] = None
|
||||
root_path: t.Optional[str] = None
|
||||
cli_group: t.Optional[str] = None
|
||||
|
||||
init_session: t.Optional[t.Dict[str, t.Any]] = None
|
||||
|
||||
_blueprint_attrs = {
|
||||
"url_prefix",
|
||||
"subdomain",
|
||||
"url_defaults",
|
||||
"static_folder",
|
||||
"template_folder",
|
||||
"static_url_path",
|
||||
"root_path",
|
||||
"cli_group",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
enabled: bool = False,
|
||||
url_prefix: t.Optional[str] = None,
|
||||
subdomain: t.Optional[str] = None,
|
||||
url_defaults: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
static_folder: t.Optional[str] = None,
|
||||
template_folder: t.Optional[str] = None,
|
||||
static_url_path: t.Optional[str] = None,
|
||||
root_path: t.Optional[str] = None,
|
||||
cli_group: t.Optional[str] = None,
|
||||
init_session: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
):
|
||||
self.enabled = enabled
|
||||
self.url_prefix = url_prefix
|
||||
self.subdomain = subdomain
|
||||
self.url_defaults = url_defaults
|
||||
self.static_folder = static_folder
|
||||
self.template_folder = template_folder
|
||||
self.static_url_path = static_url_path
|
||||
self.root_path = root_path
|
||||
self.cli_group = cli_group
|
||||
self.init_session = init_session
|
||||
|
||||
def quart_blueprint_args(self) -> t.Dict[str, t.Any]:
|
||||
return {k: getattr(self, k) for k in self._blueprint_attrs if getattr(self, k)}
|
||||
14
src/quart_imp/config/imp_config.py
Normal file
14
src/quart_imp/config/imp_config.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import typing as t
|
||||
|
||||
|
||||
class ImpConfig:
|
||||
IMP_INIT_SESSION: t.Optional[t.Dict[str, t.Any]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
init_session: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
):
|
||||
if not init_session:
|
||||
self.IMP_INIT_SESSION = {}
|
||||
else:
|
||||
self.IMP_INIT_SESSION = init_session
|
||||
162
src/quart_imp/config/quart_config.py
Normal file
162
src/quart_imp/config/quart_config.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import typing as t
|
||||
|
||||
from quart import Quart
|
||||
|
||||
|
||||
class QuartConfig:
|
||||
DEBUG: t.Optional[bool]
|
||||
PROPAGATE_EXCEPTIONS: t.Optional[bool]
|
||||
TRAP_HTTP_EXCEPTIONS: t.Optional[bool]
|
||||
TRAP_BAD_REQUEST_ERRORS: t.Optional[bool]
|
||||
SECRET_KEY: t.Optional[str]
|
||||
SESSION_COOKIE_NAME: t.Optional[str]
|
||||
SESSION_COOKIE_DOMAIN: t.Optional[str]
|
||||
SESSION_COOKIE_PATH: t.Optional[str]
|
||||
SESSION_COOKIE_HTTPONLY: t.Optional[bool]
|
||||
SESSION_COOKIE_SECURE: t.Optional[bool]
|
||||
SESSION_COOKIE_SAMESITE: t.Optional[t.Literal["Lax", "Strict"]]
|
||||
PERMANENT_SESSION_LIFETIME: t.Optional[int]
|
||||
SESSION_REFRESH_EACH_REQUEST: t.Optional[bool]
|
||||
USE_X_SENDFILE: t.Optional[bool]
|
||||
SEND_FILE_MAX_AGE_DEFAULT: t.Optional[int]
|
||||
ERROR_404_HELP: t.Optional[bool]
|
||||
SERVER_NAME: t.Optional[str]
|
||||
APPLICATION_ROOT: t.Optional[str]
|
||||
PREFERRED_URL_SCHEME: t.Optional[str]
|
||||
MAX_CONTENT_LENGTH: t.Optional[int]
|
||||
TEMPLATES_AUTO_RELOAD: t.Optional[bool]
|
||||
EXPLAIN_TEMPLATE_LOADING: t.Optional[bool]
|
||||
MAX_COOKIE_SIZE: t.Optional[int]
|
||||
|
||||
_additional: t.Dict[str, t.Any]
|
||||
|
||||
_quart_config_keys = {
|
||||
"DEBUG",
|
||||
"PROPAGATE_EXCEPTIONS",
|
||||
"TRAP_HTTP_EXCEPTIONS",
|
||||
"TRAP_BAD_REQUEST_ERRORS",
|
||||
"SECRET_KEY",
|
||||
"SESSION_COOKIE_NAME",
|
||||
"SESSION_COOKIE_DOMAIN",
|
||||
"SESSION_COOKIE_PATH",
|
||||
"SESSION_COOKIE_HTTPONLY",
|
||||
"SESSION_COOKIE_SECURE",
|
||||
"SESSION_COOKIE_SAMESITE",
|
||||
"PERMANENT_SESSION_LIFETIME",
|
||||
"SESSION_REFRESH_EACH_REQUEST",
|
||||
"USE_X_SENDFILE",
|
||||
"SEND_FILE_MAX_AGE_DEFAULT",
|
||||
"ERROR_404_HELP",
|
||||
"SERVER_NAME",
|
||||
"APPLICATION_ROOT",
|
||||
"PREFERRED_URL_SCHEME",
|
||||
"MAX_CONTENT_LENGTH",
|
||||
"TEMPLATES_AUTO_RELOAD",
|
||||
"EXPLAIN_TEMPLATE_LOADING",
|
||||
"MAX_COOKIE_SIZE",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
debug: t.Optional[bool] = None,
|
||||
propagate_exceptions: t.Optional[bool] = None,
|
||||
trap_http_exceptions: t.Optional[bool] = None,
|
||||
trap_bad_request_errors: t.Optional[bool] = None,
|
||||
secret_key: t.Optional[str] = None,
|
||||
session_cookie_name: t.Optional[str] = None,
|
||||
session_cookie_domain: t.Optional[str] = None,
|
||||
session_cookie_path: t.Optional[str] = None,
|
||||
session_cookie_httponly: t.Optional[bool] = None,
|
||||
session_cookie_secure: t.Optional[bool] = None,
|
||||
session_cookie_samesite: t.Optional[t.Literal["Lax", "Strict"]] = None,
|
||||
permanent_session_lifetime: t.Optional[int] = None,
|
||||
session_refresh_each_request: t.Optional[bool] = None,
|
||||
use_x_sendfile: t.Optional[bool] = None,
|
||||
send_file_max_age_default: t.Optional[int] = None,
|
||||
error_404_help: t.Optional[bool] = None,
|
||||
server_name: t.Optional[str] = None,
|
||||
application_root: t.Optional[str] = None,
|
||||
preferred_url_scheme: t.Optional[str] = None,
|
||||
max_content_length: t.Optional[int] = None,
|
||||
templates_auto_reload: t.Optional[bool] = None,
|
||||
explain_template_loading: t.Optional[bool] = None,
|
||||
max_cookie_size: t.Optional[int] = None,
|
||||
app_instance: t.Optional["Quart"] = None,
|
||||
additional: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Quart configuration class modeled after the Quart documentation.
|
||||
|
||||
Additional config values can be set by passing them as a dict or using the set_additional method.
|
||||
|
||||
All key arguments are converted to uppercase and added to the Quart app.config dictionary.
|
||||
e.g. session_cookie_name -> app.config["SESSION_COOKIE_NAME"]
|
||||
"""
|
||||
self.DEBUG = debug
|
||||
self.PROPAGATE_EXCEPTIONS = propagate_exceptions
|
||||
self.TRAP_HTTP_EXCEPTIONS = trap_http_exceptions
|
||||
self.TRAP_BAD_REQUEST_ERRORS = trap_bad_request_errors
|
||||
self.SECRET_KEY = secret_key
|
||||
self.SESSION_COOKIE_NAME = session_cookie_name
|
||||
self.SESSION_COOKIE_DOMAIN = session_cookie_domain
|
||||
self.SESSION_COOKIE_PATH = session_cookie_path
|
||||
self.SESSION_COOKIE_HTTPONLY = session_cookie_httponly
|
||||
self.SESSION_COOKIE_SECURE = session_cookie_secure
|
||||
self.SESSION_COOKIE_SAMESITE = session_cookie_samesite
|
||||
self.PERMANENT_SESSION_LIFETIME = permanent_session_lifetime
|
||||
self.SESSION_REFRESH_EACH_REQUEST = session_refresh_each_request
|
||||
self.USE_X_SENDFILE = use_x_sendfile
|
||||
self.SEND_FILE_MAX_AGE_DEFAULT = send_file_max_age_default
|
||||
self.ERROR_404_HELP = error_404_help
|
||||
self.SERVER_NAME = server_name
|
||||
self.APPLICATION_ROOT = application_root
|
||||
self.PREFERRED_URL_SCHEME = preferred_url_scheme
|
||||
self.MAX_CONTENT_LENGTH = max_content_length
|
||||
self.TEMPLATES_AUTO_RELOAD = templates_auto_reload
|
||||
self.EXPLAIN_TEMPLATE_LOADING = explain_template_loading
|
||||
self.MAX_COOKIE_SIZE = max_cookie_size
|
||||
self._additional = additional or {}
|
||||
|
||||
if app_instance is not None:
|
||||
self.init_app(app_instance)
|
||||
|
||||
def set_additional(self, _auto_uppercase: bool = True, **kwargs: t.Any) -> None:
|
||||
"""
|
||||
Set additional config values that are not part of the QuartConfig class.
|
||||
Keys are converted to uppercase.
|
||||
"""
|
||||
if kwargs:
|
||||
self._additional.update(
|
||||
{
|
||||
k.upper()
|
||||
if isinstance(k, str)
|
||||
else k
|
||||
if _auto_uppercase
|
||||
else k and k.upper() not in self._quart_config_keys: v
|
||||
for k, v in kwargs.items()
|
||||
}
|
||||
)
|
||||
|
||||
def init_app(self, app: Quart) -> None:
|
||||
if not isinstance(app, Quart):
|
||||
raise TypeError("The app that was passed in is not an instance of Quart")
|
||||
self.apply_config(app)
|
||||
|
||||
def apply_config(self, app: Quart) -> None:
|
||||
if not isinstance(app, Quart):
|
||||
raise TypeError("The app that was passed in is not an instance of Quart")
|
||||
|
||||
app.config.update(
|
||||
{
|
||||
**{k: v for k, v in self.as_dict().items() if v is not None},
|
||||
**{k.upper(): v for k, v in self._additional.items()},
|
||||
}
|
||||
)
|
||||
|
||||
def as_dict(self) -> t.Dict[str, t.Any]:
|
||||
return {
|
||||
**{
|
||||
k: getattr(self, k) for k in self._quart_config_keys if getattr(self, k)
|
||||
},
|
||||
**self._additional,
|
||||
}
|
||||
2
src/quart_imp/exceptions.py
Normal file
2
src/quart_imp/exceptions.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class NoConfigProvided(Exception):
|
||||
pass
|
||||
294
src/quart_imp/imp.py
Normal file
294
src/quart_imp/imp.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import asyncio
|
||||
import typing as t
|
||||
from importlib import import_module
|
||||
from inspect import getmembers
|
||||
from pathlib import Path
|
||||
|
||||
from quart import Quart, Blueprint, session
|
||||
|
||||
from .config import ImpConfig
|
||||
from .imp_blueprint import ImpBlueprint
|
||||
from .utilities import cast_to_import_str
|
||||
|
||||
|
||||
class Imp:
|
||||
app: Quart
|
||||
app_name: str
|
||||
app_path: Path
|
||||
app_instance_path: Path
|
||||
app_folder: Path
|
||||
app_resources_imported: bool = False
|
||||
|
||||
config: ImpConfig
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: t.Optional[Quart] = None,
|
||||
config: t.Optional[ImpConfig] = None,
|
||||
) -> None:
|
||||
if app is not None:
|
||||
self.init_app(app, config)
|
||||
|
||||
def init_app(
|
||||
self,
|
||||
app: Quart,
|
||||
config: t.Optional[ImpConfig] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the app with the quart-imp extension.
|
||||
|
||||
:param app: The quart app to initialize.
|
||||
:param config: The config to use
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if app is None:
|
||||
raise ImportError(
|
||||
"No app was passed in, do imp = Imp(quartapp) or app.init_app(quartapp)"
|
||||
)
|
||||
if not isinstance(app, Quart):
|
||||
raise TypeError("The app that was passed in is not an instance of Quart")
|
||||
|
||||
if "imp" in app.extensions:
|
||||
raise ImportError("The app has already been initialized with quart-imp.")
|
||||
|
||||
self.app = app
|
||||
self.app_name = app.name
|
||||
self.app_path = Path(self.app.root_path)
|
||||
self.app_instance_path = Path(self.app.instance_path)
|
||||
self.app_folder = self.app_path.parent
|
||||
self.app.extensions["imp"] = self
|
||||
|
||||
if config:
|
||||
self.config = config
|
||||
else:
|
||||
self.config = ImpConfig()
|
||||
|
||||
self._init_session()
|
||||
|
||||
self.app_instance_path.mkdir(exist_ok=True)
|
||||
|
||||
def import_app_resources(
|
||||
self,
|
||||
folder: str = "resources",
|
||||
factories: t.Optional[t.List[str]] = None,
|
||||
static_folder: str = "static",
|
||||
templates_folder: str = "templates",
|
||||
scope_import: t.Optional[t.Dict[str, t.Union[t.List[str], str]]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Imports the app resources from the given folder.
|
||||
|
||||
:param folder: The folder to import from, must be relative.
|
||||
:param factories: A list of function names to call with the app instance.
|
||||
:param static_folder: The name of the static folder (if not found will be set to None)
|
||||
:param templates_folder: The name of the templates folder (if not found will be set to None)
|
||||
:param scope_import: A dict of files to import e.g. {"folder_name": "*"}.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Check if the app resources have already been imported
|
||||
if self.app_resources_imported:
|
||||
raise ImportError("The app resources can only be imported once.")
|
||||
|
||||
self.app_resources_imported = True
|
||||
|
||||
# Set defaults
|
||||
if factories is None:
|
||||
factories = []
|
||||
if scope_import is None:
|
||||
scope_import = {"*": ["*"]}
|
||||
|
||||
# Build folders
|
||||
resources_folder = self.app_path / folder
|
||||
app_static_folder = resources_folder / static_folder
|
||||
app_templates_folder = resources_folder / templates_folder
|
||||
|
||||
if not resources_folder.exists():
|
||||
raise ImportError(
|
||||
f"Cannot find resources collection folder at: {resources_folder}"
|
||||
)
|
||||
|
||||
if not resources_folder.is_dir():
|
||||
raise ImportError(
|
||||
f"Resources collection must be a folder, value given: {resources_folder}"
|
||||
)
|
||||
|
||||
self.app.static_folder = (
|
||||
app_static_folder.as_posix() if app_static_folder.exists() else None
|
||||
)
|
||||
self.app.template_folder = (
|
||||
app_templates_folder.as_posix() if app_templates_folder.exists() else None
|
||||
)
|
||||
|
||||
skip_folders = (
|
||||
"static",
|
||||
"templates",
|
||||
)
|
||||
|
||||
for item in resources_folder.iterdir():
|
||||
if item.name.startswith("__"):
|
||||
continue
|
||||
|
||||
if item.is_file() and item.suffix == ".py":
|
||||
if "*" in scope_import:
|
||||
if "*" in scope_import["*"]:
|
||||
self._import_resource_module(item, factories)
|
||||
else:
|
||||
if item.name in scope_import["*"]:
|
||||
self._import_resource_module(item, factories)
|
||||
|
||||
if "." in scope_import:
|
||||
if "*" in scope_import["."]:
|
||||
self._import_resource_module(item, factories)
|
||||
else:
|
||||
if item.name in scope_import["."]:
|
||||
self._import_resource_module(item, factories)
|
||||
|
||||
if item.is_dir():
|
||||
# skip the static and templates folders
|
||||
if item.name in skip_folders:
|
||||
continue
|
||||
|
||||
for py_file_in_item in item.glob("*.py"):
|
||||
if "*" in scope_import:
|
||||
if "*" in scope_import["*"]:
|
||||
self._import_resource_module(py_file_in_item, factories)
|
||||
else:
|
||||
if py_file_in_item.name in scope_import["*"]:
|
||||
self._import_resource_module(py_file_in_item, factories)
|
||||
|
||||
if item.name in scope_import:
|
||||
if "*" in scope_import[item.name]:
|
||||
self._import_resource_module(py_file_in_item, factories)
|
||||
else:
|
||||
if py_file_in_item.name in scope_import[item.name]:
|
||||
self._import_resource_module(py_file_in_item, factories)
|
||||
|
||||
def import_blueprint(self, blueprint: str) -> None:
|
||||
"""
|
||||
Import a blueprint from the given package.
|
||||
|
||||
:param blueprint: The blueprint (folder name) to import. Must be relative.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if Path(blueprint).is_absolute():
|
||||
blueprint_path = Path(blueprint)
|
||||
else:
|
||||
blueprint_path = Path(self.app_path / blueprint)
|
||||
|
||||
if blueprint_path.exists() and blueprint_path.is_dir():
|
||||
module = import_module(cast_to_import_str(self.app_name, blueprint_path))
|
||||
for name, potential_blueprint in getmembers(module):
|
||||
if isinstance(potential_blueprint, ImpBlueprint):
|
||||
self._imp_blueprint_registration(potential_blueprint)
|
||||
continue
|
||||
|
||||
if isinstance(potential_blueprint, Blueprint):
|
||||
self._quart_blueprint_registration(potential_blueprint)
|
||||
|
||||
def import_blueprints(self, folder: str) -> None:
|
||||
"""
|
||||
Import all blueprints from the given folder.
|
||||
|
||||
:param folder: The folder to import from. Must be relative.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
folder_path = Path(self.app_path / folder)
|
||||
|
||||
if not folder_path.exists():
|
||||
raise ImportError(f"Cannot find blueprints folder at {folder_path}")
|
||||
|
||||
if not folder_path.is_dir():
|
||||
raise ImportError(f"Blueprints must be a folder {folder_path}")
|
||||
|
||||
for potential_bp in folder_path.iterdir():
|
||||
self.import_blueprint(f"{potential_bp}")
|
||||
|
||||
async def _async_import_resource_module(
|
||||
self, module: Path, factories: t.List[str]
|
||||
) -> None:
|
||||
try:
|
||||
async with self.app.app_context():
|
||||
file_module = import_module(cast_to_import_str(self.app_name, module))
|
||||
|
||||
for instance_factory in factories:
|
||||
if hasattr(file_module, instance_factory):
|
||||
getattr(file_module, instance_factory)(self.app)
|
||||
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Error when importing {module}: {e}")
|
||||
|
||||
def _import_resource_module(self, module: Path, factories: t.List[str]) -> None:
|
||||
asyncio.run(self._async_import_resource_module(module, factories))
|
||||
|
||||
def _imp_blueprint_registration(self, imp_blueprint: ImpBlueprint) -> None:
|
||||
if not imp_blueprint.config.enabled:
|
||||
self.app.logger.debug(
|
||||
f"Imp Blueprint [{imp_blueprint.bp_name}] is disabled."
|
||||
)
|
||||
return
|
||||
|
||||
for nested_blueprint in imp_blueprint.nested_blueprints:
|
||||
if isinstance(nested_blueprint, ImpBlueprint):
|
||||
self._nested_imp_blueprint_registration(imp_blueprint, nested_blueprint)
|
||||
|
||||
elif isinstance(nested_blueprint, Blueprint):
|
||||
self._nested_quart_blueprint_registration(
|
||||
nested_blueprint, nested_blueprint
|
||||
)
|
||||
|
||||
if imp_blueprint.config.init_session:
|
||||
self.config.IMP_INIT_SESSION.update(
|
||||
imp_blueprint.config.init_session
|
||||
) if isinstance(self.config.IMP_INIT_SESSION, dict) else None
|
||||
|
||||
self._quart_blueprint_registration(imp_blueprint)
|
||||
|
||||
def _nested_imp_blueprint_registration(
|
||||
self,
|
||||
parent: ImpBlueprint,
|
||||
child: ImpBlueprint,
|
||||
) -> None:
|
||||
if not parent.config.enabled:
|
||||
return
|
||||
|
||||
if not child.config.enabled:
|
||||
self.app.logger.debug(
|
||||
f"Imp Blueprint [{child.bp_name}] is disabled. Parent: [{parent.bp_name}]"
|
||||
)
|
||||
return
|
||||
|
||||
parent.register_blueprint(child)
|
||||
|
||||
for partial_model in child.models:
|
||||
partial_model(imp_instance=self)
|
||||
|
||||
if child.config.init_session:
|
||||
if self.config.IMP_INIT_SESSION is None:
|
||||
self.config.IMP_INIT_SESSION = {}
|
||||
|
||||
self.config.IMP_INIT_SESSION.update(child.config.init_session)
|
||||
|
||||
def _quart_blueprint_registration(self, blueprint: Blueprint) -> None:
|
||||
self.app.register_blueprint(blueprint)
|
||||
|
||||
@staticmethod
|
||||
def _nested_quart_blueprint_registration(
|
||||
parent: Blueprint,
|
||||
child: Blueprint,
|
||||
) -> None:
|
||||
parent.register_blueprint(blueprint=child)
|
||||
|
||||
def _init_session(self) -> None:
|
||||
"""
|
||||
:return: None
|
||||
"""
|
||||
if isinstance(self.config.IMP_INIT_SESSION, dict):
|
||||
_: t.Dict[str, t.Any] = self.config.IMP_INIT_SESSION
|
||||
|
||||
@self.app.before_request
|
||||
async def imp_before_request() -> None:
|
||||
session.update({k: v for k, v in _.items() if k not in session})
|
||||
167
src/quart_imp/imp_blueprint.py
Normal file
167
src/quart_imp/imp_blueprint.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import typing as t
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
from inspect import getmembers
|
||||
from pathlib import Path
|
||||
|
||||
from quart import Blueprint
|
||||
|
||||
from .config import ImpBlueprintConfig
|
||||
from .exceptions import NoConfigProvided
|
||||
from .utilities import (
|
||||
cast_to_import_str,
|
||||
slug,
|
||||
)
|
||||
|
||||
ArgT = t.TypeVar("ArgT")
|
||||
ReturnT = t.TypeVar("ReturnT")
|
||||
|
||||
|
||||
class ImpBlueprint(Blueprint):
|
||||
"""
|
||||
A Class that extends the capabilities of the Quart Blueprint class.
|
||||
"""
|
||||
|
||||
config: ImpBlueprintConfig
|
||||
|
||||
location: Path
|
||||
bp_name: str
|
||||
package: str
|
||||
|
||||
models: t.Set[t.Any]
|
||||
nested_blueprints: t.Set[t.Union["ImpBlueprint", Blueprint]]
|
||||
|
||||
def __init__(self, dunder_name: str, config: ImpBlueprintConfig) -> None:
|
||||
"""
|
||||
Initializes the ImpBlueprint.
|
||||
|
||||
:param dunder_name: __name__
|
||||
:param config: The blueprint's config.
|
||||
"""
|
||||
|
||||
self.models = set()
|
||||
self.nested_blueprints = set()
|
||||
|
||||
self.package = dunder_name
|
||||
|
||||
spec = find_spec(self.package)
|
||||
if spec is None:
|
||||
raise ImportError(f"Cannot find origin of {self.package}")
|
||||
|
||||
self.location = Path(f"{spec.origin}").parent
|
||||
self.bp_name = self.location.name
|
||||
|
||||
if config is None:
|
||||
raise NoConfigProvided(f"No config was provided for {self.location}")
|
||||
|
||||
self.config = config
|
||||
|
||||
if not self.config.url_prefix:
|
||||
self.config.url_prefix = f"/{slug(self.bp_name)}"
|
||||
|
||||
super().__init__(
|
||||
self.bp_name, self.package, **self.config.quart_blueprint_args()
|
||||
)
|
||||
|
||||
def _prevent_if_disabled(self: "ImpBlueprint") -> bool:
|
||||
if not self.config.enabled:
|
||||
return True
|
||||
return False
|
||||
|
||||
def as_quart_blueprint(self) -> Blueprint:
|
||||
"""
|
||||
Returns the blueprint as a Quart Blueprint.
|
||||
|
||||
:return: Blueprint
|
||||
"""
|
||||
return self
|
||||
|
||||
def import_resources(self, folder: str = "routes") -> None:
|
||||
"""
|
||||
Will import all the resources (cli, routes, filters, context_processors...) from the given folder.
|
||||
Given folder must be relative to the blueprint (in the same folder as the __init__.py file).
|
||||
|
||||
:param folder: Folder to look for resources in. Defaults to "routes". Must be relative.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if self._prevent_if_disabled():
|
||||
return
|
||||
|
||||
resource_path = self.location / folder
|
||||
if not resource_path.exists():
|
||||
raise NotADirectoryError(f"{resource_path} is not a directory")
|
||||
|
||||
resources = resource_path.glob("*.py")
|
||||
for resource in resources:
|
||||
try:
|
||||
import_module(f"{self.package}.{folder}.{resource.stem}")
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
f"Error when importing {self.package}.{resource}: {e}"
|
||||
)
|
||||
|
||||
def import_nested_blueprint(self, blueprint: t.Union[str, Path]) -> None:
|
||||
"""
|
||||
Imports the specified Quart-Imp Blueprint or a standard Quart Blueprint as a nested blueprint,
|
||||
under the current blueprint.
|
||||
|
||||
:param blueprint: The blueprint (folder name) to import. Must be relative.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if self._prevent_if_disabled():
|
||||
return
|
||||
|
||||
if isinstance(blueprint, Path):
|
||||
potential_bp = blueprint
|
||||
else:
|
||||
if isinstance(blueprint, str):
|
||||
if Path(blueprint).is_absolute():
|
||||
potential_bp = Path(blueprint)
|
||||
else:
|
||||
potential_bp = Path(self.location / blueprint)
|
||||
else:
|
||||
raise ValueError("Blueprint must be a string or a Path object")
|
||||
|
||||
if potential_bp.exists() and potential_bp.is_dir():
|
||||
module = import_module(
|
||||
cast_to_import_str(self.package.split(".")[0], potential_bp)
|
||||
)
|
||||
for name, potential in getmembers(module):
|
||||
if isinstance(potential, ImpBlueprint):
|
||||
self.nested_blueprints.add(potential)
|
||||
continue
|
||||
|
||||
if isinstance(potential, Blueprint):
|
||||
self.nested_blueprints.add(potential)
|
||||
|
||||
def import_nested_blueprints(self, folder: str) -> None:
|
||||
"""
|
||||
Imports all blueprints in the given folder.
|
||||
|
||||
:param folder: Folder to look for nested blueprints in.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if self._prevent_if_disabled():
|
||||
return
|
||||
|
||||
folder_path = Path(self.location / folder)
|
||||
|
||||
if not folder_path.exists() or not folder_path.is_dir():
|
||||
raise NotADirectoryError(f"{folder_path} is not a directory")
|
||||
|
||||
for potential_bp in folder_path.iterdir():
|
||||
self.import_nested_blueprint(blueprint=potential_bp)
|
||||
|
||||
def tmpl(self, template: str) -> str:
|
||||
"""
|
||||
Pushes the blueprint name to the template name.
|
||||
This saves time in having to type out the blueprint name when rendering a
|
||||
template file from the blueprint's template folder.
|
||||
|
||||
:param template: The template name to push the blueprint name to.
|
||||
:return: str - The template name with the blueprint name pushed to it.
|
||||
"""
|
||||
return f"{self.name}/{template}"
|
||||
101
src/quart_imp/protocols.py
Normal file
101
src/quart_imp/protocols.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
from quart import Quart, Blueprint
|
||||
|
||||
ImpBlueprintSelf = t.TypeVar("ImpBlueprintSelf", bound="ImpBlueprint")
|
||||
|
||||
|
||||
@t.runtime_checkable
|
||||
class ImpBlueprint(t.Protocol):
|
||||
config: "ImpBlueprintConfig"
|
||||
|
||||
location: Path
|
||||
bp_name: str
|
||||
package: str
|
||||
|
||||
models: t.Set[t.Any]
|
||||
nested_blueprints: t.Set[t.Union["ImpBlueprint", Blueprint]]
|
||||
|
||||
def _prevent_if_disabled(self: "ImpBlueprint") -> bool: ...
|
||||
|
||||
def as_quart_blueprint(self) -> Blueprint: ...
|
||||
|
||||
def import_resources(self, folder: str = "routes") -> None: ...
|
||||
|
||||
def import_nested_blueprint(self, blueprint: str) -> None: ...
|
||||
|
||||
def import_nested_blueprints(self, folder: str) -> None: ...
|
||||
|
||||
def tmpl(self, template: str) -> str: ...
|
||||
|
||||
|
||||
@t.runtime_checkable
|
||||
class Imp(t.Protocol):
|
||||
app: Quart
|
||||
config: t.Any
|
||||
app_instance_path: Path
|
||||
app_path: Path
|
||||
|
||||
|
||||
@t.runtime_checkable
|
||||
class QuartConfig(t.Protocol):
|
||||
DEBUG: t.Optional[bool]
|
||||
PROPAGATE_EXCEPTIONS: t.Optional[bool]
|
||||
TRAP_HTTP_EXCEPTIONS: t.Optional[bool]
|
||||
TRAP_BAD_REQUEST_ERRORS: t.Optional[bool]
|
||||
SECRET_KEY: t.Optional[str]
|
||||
SESSION_COOKIE_NAME: t.Optional[str]
|
||||
SESSION_COOKIE_DOMAIN: t.Optional[str]
|
||||
SESSION_COOKIE_PATH: t.Optional[str]
|
||||
SESSION_COOKIE_HTTPONLY: t.Optional[bool]
|
||||
SESSION_COOKIE_SECURE: t.Optional[bool]
|
||||
SESSION_COOKIE_SAMESITE: t.Optional[t.Literal["Lax", "Strict"]]
|
||||
PERMANENT_SESSION_LIFETIME: t.Optional[int]
|
||||
SESSION_REFRESH_EACH_REQUEST: t.Optional[bool]
|
||||
USE_X_SENDFILE: t.Optional[bool]
|
||||
SEND_FILE_MAX_AGE_DEFAULT: t.Optional[int]
|
||||
ERROR_404_HELP: t.Optional[bool]
|
||||
SERVER_NAME: t.Optional[str]
|
||||
APPLICATION_ROOT: t.Optional[str]
|
||||
PREFERRED_URL_SCHEME: t.Optional[str]
|
||||
MAX_CONTENT_LENGTH: t.Optional[int]
|
||||
TEMPLATES_AUTO_RELOAD: t.Optional[bool]
|
||||
EXPLAIN_TEMPLATE_LOADING: t.Optional[bool]
|
||||
MAX_COOKIE_SIZE: t.Optional[int]
|
||||
|
||||
_quart_config_keys: t.Set[str]
|
||||
|
||||
def apply_config(self, app: Quart) -> None: ...
|
||||
|
||||
def as_dict(self) -> t.Dict[str, t.Any]: ...
|
||||
|
||||
|
||||
@t.runtime_checkable
|
||||
class ImpConfig(t.Protocol):
|
||||
FLASK: QuartConfig
|
||||
|
||||
INIT_SESSION: t.Optional[t.Dict[str, t.Any]]
|
||||
|
||||
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: ...
|
||||
|
||||
|
||||
@t.runtime_checkable
|
||||
class ImpBlueprintConfig(t.Protocol):
|
||||
enabled: t.Optional[bool]
|
||||
url_prefix: t.Optional[str]
|
||||
subdomain: t.Optional[str]
|
||||
url_default: t.Optional[t.Dict[str, t.Any]]
|
||||
static_folder: t.Optional[str]
|
||||
template_folder: t.Optional[str]
|
||||
static_url_path: t.Optional[str]
|
||||
root_path: t.Optional[str]
|
||||
cli_group: t.Optional[str]
|
||||
|
||||
init_session: t.Optional[t.Dict[str, t.Any]]
|
||||
|
||||
_blueprint_attrs: t.Set[str]
|
||||
|
||||
def quart_blueprint_args(self) -> t.Dict[str, t.Any]: ...
|
||||
13
src/quart_imp/security/__init__.py
Normal file
13
src/quart_imp/security/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from .api_login_check import api_login_check
|
||||
from .include_csrf import include_csrf
|
||||
from .login_check import login_check
|
||||
from .pass_function_check import pass_function_check
|
||||
from .permission_check import permission_check
|
||||
|
||||
__all__ = [
|
||||
"api_login_check",
|
||||
"include_csrf",
|
||||
"login_check",
|
||||
"pass_function_check",
|
||||
"permission_check",
|
||||
]
|
||||
25
src/quart_imp/security/__private_funcs__.py
Normal file
25
src/quart_imp/security/__private_funcs__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import typing as t
|
||||
|
||||
|
||||
def _check_against_values_allowed(
|
||||
session_value: t.Union[t.List[str], str, int, bool],
|
||||
values_allowed: t.Union[t.List[t.Union[str, int, bool]], str, int, bool],
|
||||
) -> bool:
|
||||
"""
|
||||
Checks if the session value matches the values allowed. Used by login_check and permission_check.
|
||||
"""
|
||||
if isinstance(values_allowed, list):
|
||||
if isinstance(session_value, list):
|
||||
for value in session_value:
|
||||
if value in values_allowed:
|
||||
return True
|
||||
return False
|
||||
|
||||
if session_value in values_allowed:
|
||||
return True
|
||||
return False
|
||||
|
||||
if session_value == values_allowed:
|
||||
return True
|
||||
|
||||
return False
|
||||
65
src/quart_imp/security/api_login_check.py
Normal file
65
src/quart_imp/security/api_login_check.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import typing as t
|
||||
from functools import wraps
|
||||
|
||||
from quart import session
|
||||
|
||||
from .__private_funcs__ import _check_against_values_allowed
|
||||
|
||||
|
||||
def api_login_check(
|
||||
session_key: str,
|
||||
values_allowed: t.Union[t.List[t.Union[str, int, bool]], str, int, bool],
|
||||
fail_json: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
) -> t.Callable[..., t.Any]:
|
||||
"""
|
||||
A decorator that is used to secure API routes that return JSON responses.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example of a route that requires a user to be logged in:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/api/resource", methods=["GET"])
|
||||
@api_login_check('logged_in', True)
|
||||
def api_page():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**You can also supply your own failed return JSON:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/api/resource", methods=["GET"])
|
||||
@api_login_check('logged_in', True, fail_json={"error": "You are not logged in."})
|
||||
def api_page():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param session_key: The session key to check for.
|
||||
:param values_allowed: A list of or singular value(s) that the session key must contain.
|
||||
:param fail_json: JSON that is returned on failure. {"error": "You are not logged in."} by default.
|
||||
:return: The decorated function, or a JSON response.
|
||||
"""
|
||||
|
||||
def api_login_check_wrapper(func: t.Any) -> t.Callable[..., t.Any]:
|
||||
@wraps(func)
|
||||
def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
skey = session.get(session_key)
|
||||
if skey is not None:
|
||||
if _check_against_values_allowed(skey, values_allowed):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return fail_json
|
||||
|
||||
return inner
|
||||
|
||||
return api_login_check_wrapper
|
||||
76
src/quart_imp/security/include_csrf.py
Normal file
76
src/quart_imp/security/include_csrf.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import typing as t
|
||||
from functools import wraps
|
||||
|
||||
from quart import abort
|
||||
from quart import request
|
||||
from quart import session
|
||||
from quart_imp.auth import generate_csrf_token
|
||||
|
||||
|
||||
def include_csrf(
|
||||
session_key: str = "csrf", form_key: str = "csrf", abort_code: int = 401
|
||||
) -> t.Callable[..., t.Any]:
|
||||
"""
|
||||
A decorator that handles CSRF protection.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
On a **GET** request, a CSRF token is generated and stored in the session key
|
||||
specified by the session_key parameter.
|
||||
|
||||
On a **POST** request, the form_key specified is checked against the session_key
|
||||
specified.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
| If they match, the request is allowed to continue.
|
||||
| If no match, the response will be abort(abort_code), default 401.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/admin", methods=["GET", "POST"])
|
||||
@include_csrf(session_key="csrf", form_key="csrf")
|
||||
def admin_page():
|
||||
...
|
||||
# You must pass in the CSRF token from the session into the template.
|
||||
# Then add <input type="hidden" name="csrf" value="{{ csrf }}"> to the form.
|
||||
return render_template("admin.html", csrf=session.get("csrf"))
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param session_key: The session key to store the CSRF token in.
|
||||
:param form_key: The form key to check against the session key.
|
||||
:param abort_code: The abort code to use if the CSRF check fails.
|
||||
:return: The decorated function, or abort(abort_code).
|
||||
"""
|
||||
|
||||
def include_csrf_wrapper(func: t.Any) -> t.Callable[..., t.Any]:
|
||||
@wraps(func)
|
||||
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
if request.method == "GET":
|
||||
session[session_key] = generate_csrf_token()
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if request.method == "POST":
|
||||
_session_key = session.get(session_key)
|
||||
_form_key = (await request.form).get(form_key)
|
||||
|
||||
if _form_key is None:
|
||||
return abort(abort_code)
|
||||
|
||||
if _session_key is None:
|
||||
return abort(abort_code)
|
||||
|
||||
if _session_key != _form_key:
|
||||
return abort(abort_code)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
return include_csrf_wrapper
|
||||
147
src/quart_imp/security/login_check.py
Normal file
147
src/quart_imp/security/login_check.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import typing as t
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
|
||||
from quart import abort
|
||||
from quart import flash
|
||||
from quart import redirect
|
||||
from quart import session
|
||||
from quart import url_for
|
||||
|
||||
from .__private_funcs__ import _check_against_values_allowed
|
||||
|
||||
|
||||
def login_check(
|
||||
session_key: str,
|
||||
values_allowed: t.Union[t.List[t.Union[str, int, bool]], str, int, bool],
|
||||
fail_endpoint: t.Optional[str] = None,
|
||||
pass_endpoint: t.Optional[str] = None,
|
||||
endpoint_kwargs: t.Optional[t.Dict[str, t.Union[str, int]]] = None,
|
||||
message: t.Optional[str] = None,
|
||||
message_category: str = "message",
|
||||
) -> t.Callable[..., t.Any]:
|
||||
"""
|
||||
A decorator that checks if the specified session key exists and contains the specified value.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example of a route that requires a user to be logged in:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/admin", methods=["GET"])
|
||||
@login_check('logged_in', True, fail_endpoint='blueprint.login_page', message="Login needed")
|
||||
def admin_page():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example of a route that if the user is already logged in, redirects to the specified endpoint:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/login-page", methods=["GET"])
|
||||
@login_check('logged_in', True, pass_endpoint='blueprint.admin_page', message="Already logged in")
|
||||
def login_page():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param session_key: The session key to check for.
|
||||
:param values_allowed: A list of or singular value(s) that the session key must contain.
|
||||
:param fail_endpoint: The endpoint to redirect to if the session key does not exist or
|
||||
match the pass_value.
|
||||
:param pass_endpoint: The endpoint to redirect to if the session key passes.
|
||||
Used to redirect away from login pages, if already logged in.
|
||||
:param endpoint_kwargs: A dictionary of keyword arguments to pass to the redirect endpoint.
|
||||
:param message: If a message is specified, a flash message is shown.
|
||||
:param message_category: The category of the flash message.
|
||||
:return: The decorated function, or abort(403).
|
||||
"""
|
||||
|
||||
def login_check_wrapper(func: t.Any) -> t.Callable[..., t.Any]:
|
||||
@wraps(func)
|
||||
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
skey = session.get(session_key)
|
||||
|
||||
async def setup_flash(
|
||||
_message: t.Optional[str], _message_category: t.Optional[str]
|
||||
) -> None:
|
||||
if _message:
|
||||
partial_flash = partial(flash, _message)
|
||||
if _message_category:
|
||||
await partial_flash(_message_category)
|
||||
else:
|
||||
await partial_flash()
|
||||
|
||||
if skey is None:
|
||||
if fail_endpoint:
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
fail_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(fail_endpoint))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if skey is not None:
|
||||
if _check_against_values_allowed(skey, values_allowed):
|
||||
if pass_endpoint:
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
pass_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(pass_endpoint))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if fail_endpoint:
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
fail_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(fail_endpoint))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return abort(403)
|
||||
|
||||
return inner
|
||||
|
||||
return login_check_wrapper
|
||||
227
src/quart_imp/security/pass_function_check.py
Normal file
227
src/quart_imp/security/pass_function_check.py
Normal file
@@ -0,0 +1,227 @@
|
||||
import typing as t
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
|
||||
from quart import abort
|
||||
from quart import flash
|
||||
from quart import redirect
|
||||
from quart import url_for
|
||||
|
||||
|
||||
def pass_function_check(
|
||||
function: t.Callable[..., t.Any],
|
||||
predefined_args: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
fail_endpoint: t.Optional[str] = None,
|
||||
pass_endpoint: t.Optional[str] = None,
|
||||
endpoint_kwargs: t.Optional[t.Dict[str, t.Union[str, int]]] = None,
|
||||
message: t.Optional[str] = None,
|
||||
message_category: str = "message",
|
||||
fail_on_missing_kwargs: bool = False,
|
||||
with_app_context: bool = False,
|
||||
) -> t.Callable[..., t.Any]:
|
||||
"""
|
||||
A decorator that takes the result of a function and checks if it is True or False.
|
||||
|
||||
URL variables from `@route` will be read by this decorator.
|
||||
To use URL variables in your passed in function,
|
||||
make sure your functions argument(s) name(s) match the name(s) of the URL variable(s).
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
def check_if_number(value):
|
||||
if isinstance(value, int):
|
||||
return True
|
||||
return False
|
||||
|
||||
@bp.route("/admin-page/<int:value>", methods=["GET"])
|
||||
@login_check('logged_in', True, 'blueprint.login_page') # can be mixed with login_check
|
||||
@pass_function_check(
|
||||
check_if_number,
|
||||
predefined_args=None,
|
||||
fail_endpoint='www.index',
|
||||
message="Failed message"
|
||||
)
|
||||
def admin_page():
|
||||
...
|
||||
|
||||
@bp.route("/admin-page/<int:value>", methods=["GET"])
|
||||
@login_check('logged_in', True, 'blueprint.login_page') # can be mixed with login_check
|
||||
@pass_function_check(
|
||||
check_if_number,
|
||||
predefined_args={'value': 10},
|
||||
fail_endpoint='www.index',
|
||||
message="Failed message"
|
||||
)
|
||||
def admin_page_overwrite():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Advanced use case:**
|
||||
|
||||
Here's an example of accessing quart.session from within the passed in function. including the
|
||||
`with_app_context` parameter, the function will be called with `app_context()`.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
from quart import current_app
|
||||
from quart import session
|
||||
|
||||
...
|
||||
|
||||
def check_if_number(number=1, session_=None):
|
||||
if session_:
|
||||
print(session_)
|
||||
try:
|
||||
int(number)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@bp.route("/pass-func-check-with-url-var/<number>", methods=["GET"])
|
||||
@pass_function_check(
|
||||
check_if_number,
|
||||
predefined_args={'number': 10, 'session_': session},
|
||||
fail_endpoint="www.index",
|
||||
with_app_context=True
|
||||
)
|
||||
def admin_page_overwrite_with_session():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
If you pass in a predefined arg that has the same key name as a session variable that exists, the value
|
||||
of that predefined arg will be replaced with the session variable value.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
session['car'] = 'Toyota'
|
||||
...
|
||||
def check_function(car):
|
||||
if car == 'Toyota':
|
||||
return True
|
||||
return False
|
||||
...
|
||||
@bp.route("/pass-func-check-with-url-var/<number>", methods=["GET"])
|
||||
@pass_function_check(
|
||||
check_function,
|
||||
predefined_args={'car': session},
|
||||
...
|
||||
|
||||
This will pass, as pass_function_check will replace the value of the predefined arg 'car' with the value
|
||||
of the session variable 'car'.
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param function: The function to call (this will be passed the url variables of the route.
|
||||
:param predefined_args: A dictionary of predefined arguments to pass to the function. Any keys that match any URL
|
||||
variables will overwrite the URL variable specified in @route.
|
||||
:param fail_endpoint: The endpoint to redirect to if the
|
||||
session key does not exist or does not contain the
|
||||
specified values.
|
||||
:param pass_endpoint: The endpoint to redirect to if the function check passes.
|
||||
:param endpoint_kwargs: A dictionary of keyword arguments to pass to the redirect endpoint.
|
||||
:param message: If a message is specified, a flash message is shown.
|
||||
:param message_category: The category of the flash message.
|
||||
:param fail_on_missing_kwargs: If any of the required arguments for the passed in function are missing
|
||||
from the url variables, force function result to False.
|
||||
:param with_app_context: If True, the passed in function will be called with app_context().
|
||||
:return: The decorated function, or abort(403).
|
||||
"""
|
||||
import inspect
|
||||
from quart import current_app
|
||||
from quart.sessions import SessionMixin
|
||||
|
||||
def pass_function_wrapper(func: t.Any) -> t.Callable[..., t.Any]:
|
||||
@wraps(func)
|
||||
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
async def setup_flash(
|
||||
_message: t.Optional[str], _message_category: t.Optional[str]
|
||||
) -> None:
|
||||
if _message:
|
||||
partial_flash = partial(flash, _message)
|
||||
if _message_category:
|
||||
await partial_flash(_message_category)
|
||||
else:
|
||||
await partial_flash()
|
||||
|
||||
function_args = dict(inspect.signature(function).parameters)
|
||||
passed_in_kwargs = {k: v for k, v in kwargs.items() if k in function_args}
|
||||
|
||||
if predefined_args:
|
||||
passed_in_kwargs.update(predefined_args)
|
||||
|
||||
for key, value in passed_in_kwargs.items():
|
||||
if isinstance(value, SessionMixin):
|
||||
if with_app_context:
|
||||
async with current_app.app_context():
|
||||
if key in value:
|
||||
passed_in_kwargs[key] = value.get(key)
|
||||
|
||||
try:
|
||||
if with_app_context:
|
||||
async with current_app.app_context():
|
||||
func_result = True if function(**passed_in_kwargs) else False
|
||||
else:
|
||||
func_result = True if function(**passed_in_kwargs) else False
|
||||
|
||||
except TypeError:
|
||||
if fail_on_missing_kwargs:
|
||||
func_result = False
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if func_result:
|
||||
if pass_endpoint:
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
pass_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(pass_endpoint))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if fail_endpoint:
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
fail_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(fail_endpoint))
|
||||
|
||||
return abort(403)
|
||||
|
||||
return inner
|
||||
|
||||
return pass_function_wrapper
|
||||
94
src/quart_imp/security/permission_check.py
Normal file
94
src/quart_imp/security/permission_check.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import typing as t
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
|
||||
from quart import abort
|
||||
from quart import flash
|
||||
from quart import redirect
|
||||
from quart import session
|
||||
from quart import url_for
|
||||
|
||||
from .__private_funcs__ import _check_against_values_allowed
|
||||
|
||||
|
||||
def permission_check(
|
||||
session_key: str,
|
||||
values_allowed: t.Union[t.List[t.Union[str, int, bool]], str, int, bool],
|
||||
fail_endpoint: t.Optional[str] = None,
|
||||
endpoint_kwargs: t.Optional[t.Dict[str, t.Union[str, int]]] = None,
|
||||
message: t.Optional[str] = None,
|
||||
message_category: str = "message",
|
||||
) -> t.Callable[..., t.Any]:
|
||||
"""
|
||||
A decorator that checks if the specified session key exists and its value(s) match the specified value(s).
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
**Example:**
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
.. code-block::
|
||||
|
||||
@bp.route("/admin-page", methods=["GET"])
|
||||
@login_check('logged_in', True, 'blueprint.login_page') # can be mixed with login_check
|
||||
@permission_check('permissions', ['admin'], fail_endpoint='www.index', message="Failed message")
|
||||
def admin_page():
|
||||
...
|
||||
|
||||
:raw-html:`<br />`
|
||||
|
||||
-----
|
||||
|
||||
:param session_key: The session key to check for.
|
||||
:param values_allowed: A list of or singular value(s) that the session key must contain.
|
||||
:param fail_endpoint: The endpoint to redirect to if the
|
||||
session key does not exist or does not contain the
|
||||
specified values.
|
||||
:param endpoint_kwargs: A dictionary of keyword arguments to pass to the redirect endpoint.
|
||||
:param message: If a message is specified, a flash message is shown.
|
||||
:param message_category: The category of the flash message.
|
||||
:return: The decorated function, or abort(403).
|
||||
"""
|
||||
|
||||
def permission_check_wrapper(func: t.Any) -> t.Callable[..., t.Any]:
|
||||
@wraps(func)
|
||||
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
skey = session.get(session_key)
|
||||
|
||||
async def setup_flash(
|
||||
_message: t.Optional[str], _message_category: t.Optional[str]
|
||||
) -> None:
|
||||
if _message:
|
||||
partial_flash = partial(flash, _message)
|
||||
if _message_category:
|
||||
await partial_flash(_message_category)
|
||||
else:
|
||||
await partial_flash()
|
||||
|
||||
if skey:
|
||||
if _check_against_values_allowed(skey, values_allowed):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
await setup_flash(message, message_category)
|
||||
|
||||
if fail_endpoint:
|
||||
if endpoint_kwargs:
|
||||
return redirect(
|
||||
url_for(
|
||||
fail_endpoint,
|
||||
_anchor=None,
|
||||
_method=None,
|
||||
_scheme=None,
|
||||
_external=None,
|
||||
**endpoint_kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for(fail_endpoint))
|
||||
|
||||
return abort(403)
|
||||
|
||||
return inner
|
||||
|
||||
return permission_check_wrapper
|
||||
73
src/quart_imp/utilities.py
Normal file
73
src/quart_imp/utilities.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Sprinkles:
|
||||
HEADER = "\033[95m"
|
||||
OKBLUE = "\033[94m"
|
||||
OKCYAN = "\033[96m"
|
||||
OKGREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
BOLD = "\033[1m"
|
||||
UNDERLINE = "\033[4m"
|
||||
END = "\033[0m"
|
||||
|
||||
|
||||
def deprecated(message: str) -> t.Callable[[t.Any], t.Any]:
|
||||
def func_wrapper(func: t.Any) -> t.Any:
|
||||
@functools.wraps(func)
|
||||
def proc_function(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
logging.warning(
|
||||
f"{Sprinkles.FAIL}Function deprecated: {message}{Sprinkles.END}"
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return proc_function
|
||||
|
||||
return func_wrapper
|
||||
|
||||
|
||||
def cast_to_import_str(app_name: str, folder_path: Path) -> str:
|
||||
"""
|
||||
Takes the folder path and converts it to a string that can be imported
|
||||
"""
|
||||
folder_parts = folder_path.parts
|
||||
parts = folder_parts[folder_parts.index(app_name) :]
|
||||
if sys.version_info.major == 3:
|
||||
if sys.version_info.minor < 9:
|
||||
return ".".join(parts).replace(".py", "")
|
||||
return ".".join(parts).removesuffix(".py")
|
||||
raise NotImplementedError("Python version not supported")
|
||||
|
||||
|
||||
def snake(value: str) -> str:
|
||||
"""
|
||||
Switches name of the class CamelCase to snake_case
|
||||
"""
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", value)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
||||
|
||||
|
||||
def slug(value: str) -> str:
|
||||
"""
|
||||
Switches name of the class CamelCase to slug-case
|
||||
"""
|
||||
value = value.replace("_", "-")
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1-\2", value)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1-\2", s1).lower()
|
||||
|
||||
|
||||
def class_field(class_: str, field: str) -> str:
|
||||
"""
|
||||
Switches name of the class CamelCase to snake_case and tacks on the field name
|
||||
|
||||
Used for SQLAlchemy foreign key assignments
|
||||
|
||||
INFO ::: This function may not produce the correct information if you are using __tablename__ in your class
|
||||
"""
|
||||
return f"{snake(class_)}.{field}"
|
||||
Reference in New Issue
Block a user