diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 0000000..3e4c634 --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1,60 @@ +from pathlib import Path +from time import sleep + +import click +from flask import Flask + +from .config import Config +from ._ssg import compiler + +cwd = Path(__file__).parent + + +def create_app(): + app = Flask(__name__) + app.template_folder = "_templates" + + doc_path = Path(cwd / Config.latest) + markdown_path = Path(cwd / "_md" / Config.latest) + + @app.cli.command("compile") + @click.option("--watch", is_flag=True, help="Watch for file changes") + def compile_site(watch): + if watch: + watching_files = {} + + def change_loop(): + change = False + updated = [] + for file in markdown_path.glob("**/*.md"): + if file not in watching_files: + watching_files[file] = file.stat().st_mtime + updated.append(file) + change = True + else: + if file.stat().st_mtime > watching_files[file]: + watching_files[file] = file.stat().st_mtime + updated.append(file) + change = True + + if change: + print("Update detected, recompiling...") + for file in updated: + print(f" - {file}") + + compiler(doc_path, markdown_path) + + print("Watching for changes...") + + while True: + change_loop() + sleep(1) + + else: + compiler(doc_path, markdown_path) + + @app.route("/") + def index(): + return "To use run the following command: flask --app gdocs compile" + + return app diff --git a/docs/_md/v1/CLI Commands-quart-imp blueprint.md b/docs/_md/v1/CLI Commands-quart-imp blueprint.md new file mode 100644 index 0000000..a1b465d --- /dev/null +++ b/docs/_md/v1/CLI Commands-quart-imp blueprint.md @@ -0,0 +1,101 @@ +``` +Menu = CLI Commands/quart-imp blueprint +Title = Generate a Quart-Imp Blueprint +``` + +Quart-Imp has its own type of blueprint. It comes with some methods to auto import routes, and nested blueprints etc... +see [ImpBlueprint / Introduction](impblueprint-introduction.html) for more information. + +You have the option to generate a regular template rendering blueprint, or a API blueprint that returns a JSON response. + +```bash +quart-imp blueprint --help +``` +or +```bash +quart-imp api-blueprint --help +``` + +To generate a Quart-Imp blueprint, run the following command: + +```bash +quart-imp blueprint +``` +or +```bash +quart-imp api-blueprint +``` + +After running this command, you will be prompted to enter the location of where you want to create your blueprint: + +```text +~ $ quart-imp blueprint +(Creation is relative to the current working directory) +Folder to create blueprint in [Current Working Directory]: +``` + +As detailed in the prompt, the creation of the blueprint is relative to the current working directory. So to create a +blueprint in the folder `app/blueprints`, you would enter `app/blueprints` in the prompt. + +```text +~ $ quart-imp blueprint +(Creation is relative to the current working directory) +Folder to create blueprint in [Current Working Directory]: app/blueprints +``` + +You will then be prompted to enter a name for your blueprint: + +```text +~ $ quart-imp blueprint +... +Name of the blueprint to create [my_new_blueprint]: +``` + +The default name is 'my_new_blueprint', we will change this to 'admin' + +```text +~ $ quart-imp blueprint +... +Name of the blueprint to create [my_new_blueprint]: admin +``` + +After creating your blueprint, the folder structure will look like this: + +```text +app/ +├── blueprints +│ └── admin +│ ├── routes +│ │ └── index.py +│ │ +│ ├── static +│ │ ├── css +│ │ │ └── water.css +│ │ ├── img +│ │ │ └── quart-imp-logo.png +│ │ └── js +│ │ └── main.js +│ │ +│ ├── templates +│ │ └── www +│ │ ├── extends +│ │ │ └── main.html +│ │ ├── includes +│ │ │ ├── footer.html +│ │ │ └── header.html +│ │ └── index.html +│ │ +│ └── __init__.py +│ +... +``` + +This is a self-contained blueprint, so it has its own static, templates and routes folders. +You can now navigate '/admin' + +You can streamline this process by specifying the name of the blueprint, the folder to +create it in and the configuration to use, like so: + +```bash +quart-imp blueprint -n admin -f app/blueprints +``` \ No newline at end of file diff --git a/docs/_md/v1/CLI Commands-quart-imp init.md b/docs/_md/v1/CLI Commands-quart-imp init.md new file mode 100644 index 0000000..dd2653b --- /dev/null +++ b/docs/_md/v1/CLI Commands-quart-imp init.md @@ -0,0 +1,215 @@ +``` +Menu = CLI Commands/quart-imp init +Title = Initialising a Quart-Imp Project +``` + +Quart-Imp has a cli command that deploys a new ready-to-go project. +This project is structured in a way to give you the best idea of +how to use Quart-Imp. + +```bash +quart-imp init --help +``` + +## Create a new project + +Make sure you are in the virtual environment, and at the root of your +project folder, then run the following command: + +```bash +quart-imp init +``` + +After running this command, you will be prompted to choose what type of +app you want to deploy: + +```text +~ $ quart-imp init +What type of app would you like to create? (minimal, slim, full) [minimal]: +``` + +See below for the differences between the app types. + +After this, you will be prompted to enter a name for your app: + +```text +~ $ quart-imp init +... +What would you like to call your app? [app]: +``` + +'app' is the default name, so if you just press enter, your app will be +called 'app'. You will then see this output: + +```text +~ FILES CREATED WILL LOOP OUT HERE ~ + +=================== +Quart app deployed! +=================== + +Your app has the default name of 'app' +Quart will automatically look for this! +Run: quart run --debug + +``` + +If you called your app something other than 'app', like 'new' for example, you will see: + +```text +~ FILES CREATED WILL LOOP OUT HERE ~ + +=================== +Quart app deployed! +=================== + +Your app has the name of 'new' +Run: quart --app new run --debug + +``` + +As you can see from the output, it gives you instructions on how to start your app, +depending on the name you gave it. + +You should see a new folder that has been given the name you specified in +the `quart-imp init` command. + +### Additional options + +You can also specify a name for your app in the command itself, like so: + +```bash +quart-imp init -n my_app +``` + +This will create a new app called 'my_app'. +The default will be a minimal app, this has no blueprints. + +You can also deploy a slim app, that will have one blueprint, like so: + +```bash +quart-imp init -n my_app --slim +``` + +You can also deploy a full app that is setup for multiple blueprints, like so: + +```bash +quart-imp init -n my_app --full +``` + +## init Folder structures + +### Minimal app (default) + +`quart-imp init --minimal`: + +```text +app/ +├── resources +│ ├── static +│ │ ├── css +│ │ │ └── water.css +│ │ ├── img +│ │ │ └── quart-imp-logo.png +│ │ └── favicon.ico +│ ├── templates +│ │ └── index.html +│ └── routes.py +│ +└── __init__.py +``` + +### Slim app + +`quart-imp init --slim`: + +```text +app/ +├── extensions +│ └── __init__.py +│ +├── resources +│ ├── cli +│ │ └── cli.py +│ ├── error_handlers +│ │ └── error_handlers.py +│ ├── static +│ │ ├── css +│ │ │ └── water.css +│ │ ├── img +│ │ │ └── quart-imp-logo.png +│ │ └── favicon.ico +│ └── templates +│ └── error.html +│ +├── www +│ ├── __init__.py +│ ├── routes +│ │ └── index.py +│ ├── static +│ │ ├── css +│ │ │ └── water.css +│ │ ├── img +│ │ │ └── quart-imp-logo.png +│ │ └── js +│ │ └── main.js +│ └── templates +│ └── www +│ ├── extends +│ │ └── main.html +│ ├── includes +│ │ ├── footer.html +│ │ └── header.html +│ └── index.html +│ +└── __init__.py +``` + +### Full app + +`quart-imp init --full`: + +```text +app/ +├── blueprints +│ └── www +│ ├── __init__.py +│ ├── routes +│ │ └── index.py +│ ├── static +│ │ ├── css +│ │ │ └── water.css +│ │ ├── img +│ │ │ └── quart-imp-logo.png +│ │ └── js +│ │ └── main.js +│ └── templates +│ └── www +│ ├── extends +│ │ └── main.html +│ ├── includes +│ │ ├── footer.html +│ │ └── header.html +│ └── index.html +│ +├── extensions +│ └── __init__.py +│ +├── resources +│ ├── cli +│ │ └── cli.py +│ ├── context_processors +│ │ └── context_processors.py +│ ├── error_handlers +│ │ └── error_handlers.py +│ ├── filters +│ │ └── filters.py +│ ├── routes +│ │ └── routes.py +│ ├── static +│ │ └── favicon.ico +│ └── templates +│ └── error.html +│ +└── __init__.py +``` diff --git a/docs/_md/v1/Imp-Introduction.md b/docs/_md/v1/Imp-Introduction.md new file mode 100644 index 0000000..46144f6 --- /dev/null +++ b/docs/_md/v1/Imp-Introduction.md @@ -0,0 +1,84 @@ +``` +Menu = Imp/Introduction +Title = Quart-Imp Introduction +``` + +Quart-Imp is a Quart extension that provides auto import methods for various Quart resources. It will import +blueprints, and other resources. It uses the importlib module to achieve this. + +Quart-Imp favors the application factory pattern as a project structure, and is opinionated towards using +Blueprints. However, you can use Quart-Imp without using Blueprints. + +Here's an example of a standard Quart-Imp project structure: + +```text +app/ +├── blueprints/ +│ ├── admin/... +│ ├── api/... +│ └── www/... +├── resources/ +│ ├── filters/... +│ ├── context_processors/... +│ ├── static/... +│ └── templates/... +└── __init__.py +``` + +Here's an example of the `app/__init__.py` file: + +```python +from quart import Quart +from quart_sqlalchemy import SQLAlchemy +from quart_imp import Imp +from quart_imp.config import QuartConfig, ImpConfig + +db = SQLAlchemy() +imp = Imp() + + +def create_app(): + app = Quart(__name__) + QuartConfig( + secret_key="super_secret_key", + app_instance=app, + ) + + imp.init_app(app, config=ImpConfig( + init_session={"logged_in": False}, + )) + imp.import_app_resources("resources") + imp.import_blueprints("blueprints") + + db.init_app(app) + + return app +``` + +The Quart configuration can be loaded from any standard Quart configuration method, or from the `QuartConfig` class +shown above. + +This class contains the standard Quart configuration options found in the Quart documentation. + +The `ImpConfig` class is used to configure the `Imp` instance. + +The `init_session` option of the `ImpConfig` class is used to set the initial session variables for the Quart app. +This happens before the request is processed. + +`ImpConfig` also has the ability to set `SQLALCHEMY_DATABASE_URI` and `SQLALCHEMY_BINDS` + +For more information about the configuration setting see +[quart_imp_config-impconfig.md](quart_imp_config-impconfig.html). + +`import_app_resources` will walk one level deep into the `resources` folder, and import +all `.py` files as modules. +It will also check for the existence of a `static` and `templates` folder, and register them with the Quart app. + +There is a couple of options for `import_app_resources` to control what +is imported, see: [Imp / import_app_resources](imp-import_app_resources.html) + +`import_blueprints` expects a folder that contains many Blueprint as Python packages. +It will check each blueprint folder's `__init__.py` file for an instance of a Quart Blueprint or a +Quart-Imp Blueprint. That instant will then be registered with the Quart app. + +See more about how importing blueprints work here: [ImpBlueprint / Introduction](impblueprint-introduction.html) diff --git a/docs/_md/v1/Imp-import_app_resources.md b/docs/_md/v1/Imp-import_app_resources.md new file mode 100644 index 0000000..3b52423 --- /dev/null +++ b/docs/_md/v1/Imp-import_app_resources.md @@ -0,0 +1,106 @@ +``` +Menu = Imp/import_app_resources +Title = Imp.import_app_resources +``` + +```python +import_app_resources( + folder: str = "resources", + factories: Optional[List] = None, + static_folder: str = "static", + templates_folder: str = "templates", + files_to_import: Optional[List] = None, + folders_to_import: Optional[List] = None, + ) -> None +``` + +--- + +Import standard app resources from the specified folder. + +This will import any resources that have been set to the Quart app. + +Routes, context processors, cli, etc. + +**Can only be called once.** + +If no static and or template folder is found, the static and or template folder will be set to None in the Quart app +config. + +#### Small example of usage: + +```python +imp.import_app_resources(folder="resources") +# or +imp.import_app_resources() +# as the default folder is "resources" +``` + +Folder Structure: `resources` + +```text +app +├── resources +│ ├── routes.py +│ ├── app_fac.py +│ ├── static +│ │ └── css +│ │ └── style.css +│ └── templates +│ └── index.html +└── ... +... +``` + +File: `routes.py` + +```python +from quart import current_app as app +from quart import render_template + + +@app.route("/") +async def index(): + return await render_template("index.html") +``` + +#### How factories work + +Factories are functions that are called when importing the app resources. Here's an example: + +```python +imp.import_app_resources( + folder="resources", + factories=["development_cli"] +) +``` + +`["development_cli"]` => `development_cli(app)` function will be called, and the current app will be passed in. + +File: `app_fac.py` + +```python +def development_cli(app): + @app.cli.command("dev") + def dev(): + print("dev cli command") +``` + +#### Scoping imports + +By default, all files and folders will be imported. + +To disable this, set `files_to_import` and or +`folders_to_import` to `[None]`. + +```python +imp.import_app_resources(scope_import=[None], folders_to_import=[None]) +``` + +To scope the imports, set the `files_to_import` and or `folders_to_import` to a list of files and or folders. + +`files_to_import=["cli.py", "routes.py"]` => will only import the files `resources/cli.py` +and `resources/routes.py` + +`folders_to_import=["template_filters", "context_processors"]` => will import all files in the folders +`resources/template_filters/*.py` and `resources/context_processors/*.py` diff --git a/docs/_md/v1/Imp-import_blueprint.md b/docs/_md/v1/Imp-import_blueprint.md new file mode 100644 index 0000000..b3d165b --- /dev/null +++ b/docs/_md/v1/Imp-import_blueprint.md @@ -0,0 +1,121 @@ +``` +Menu = Imp/import_blueprint +Title = Imp.import_blueprint +``` + +```python +import_blueprint(self, blueprint: str) -> None +``` + +--- + +Import a specified Quart-Imp or standard Quart Blueprint relative to the Quart app root. + + +```text +app +├── my_blueprint +│ ├── ... +│ └── __init__.py +├── ... +└── __init__.py +``` + +File: `app/__init__.py` + +```python +from quart import Quart + +from quart_imp import Imp + +imp = Imp() + + +def create_app(): + app = Quart(__name__) + imp.init_app(app) + + imp.import_blueprint("my_blueprint") + + return app +``` + +Quart-Imp Blueprints have the ability to auto import resources, and initialize session variables. + +For more information on how Quart-Imp Blueprints work, see the [ImpBlueprint / Introduction](impblueprint-introduction.html) + +##### Example of 'my_blueprint' as a Quart-Imp Blueprint: + +```text +app +├── my_blueprint +│ ├── routes +│ │ └── index.py +│ ├── static +│ │ └── css +│ │ └── style.css +│ ├── templates +│ │ └── my_blueprint +│ │ └── index.html +│ ├── __init__.py +│ └── config.toml +└── ... +``` + +File: `__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint( + __name__, + ImpBlueprintConfig( + enabled=True, + url_prefix="/my-blueprint", + static_folder="static", + template_folder="templates", + static_url_path="/static/my_blueprint", + init_session={"my_blueprint": "session_value"}, + ), +) + +bp.import_resources("routes") +``` + +File: `routes / index.py` + +```python +from .. import bp + + +@bp.route("/") +async def index(): + return "regular_blueprint" +``` + +##### Example of 'my_blueprint' as a standard Quart Blueprint: + +```text +app +├── my_blueprint +│ ├── ... +│ └── __init__.py +└── ... +``` + +File: `__init__.py` + +```python +from quart import Blueprint + +bp = Blueprint("my_blueprint", __name__, url_prefix="/my-blueprint") + + +@bp.route("/") +async def index(): + return "regular_blueprint" +``` + +Both of the above examples will work with `imp.import_blueprint("my_blueprint")`, they will be registered +with the Quart app, and will be accessible via `url_for("my_blueprint.index")`. \ No newline at end of file diff --git a/docs/_md/v1/Imp-import_blueprints.md b/docs/_md/v1/Imp-import_blueprints.md new file mode 100644 index 0000000..333da2e --- /dev/null +++ b/docs/_md/v1/Imp-import_blueprints.md @@ -0,0 +1,50 @@ +``` +Menu = Imp/import_blueprints +Title = Imp.import_blueprints +``` + +```python +import_blueprints(self, folder: str) -> None +``` + +--- + +Import all Quart-Imp or standard Quart Blueprints from a specified folder relative to the Quart app root. + +```text +app/ +├── blueprints/ +│ ├── admin/ +│ │ ├── ... +│ │ └── __init__.py +│ ├── www/ +│ │ ├── ... +│ │ └── __init__.py +│ └── api/ +│ ├── ... +│ └── __init__.py +├── ... +└── __init__.py +``` + +File: `app/__init__.py` + +```python +from quart import Quart + +from quart_imp import Imp + +imp = Imp() + + +def create_app(): + app = Quart(__name__) + imp.init_app(app) + + imp.import_blueprints("blueprints") + + return app +``` + +This will import all Blueprints from the `blueprints` folder using the `Imp.import_blueprint` method. +See [Imp / import_blueprint](imp-import_blueprint.html) for more information. \ No newline at end of file diff --git a/docs/_md/v1/Imp-init_app-init.md b/docs/_md/v1/Imp-init_app-init.md new file mode 100644 index 0000000..7c334c0 --- /dev/null +++ b/docs/_md/v1/Imp-init_app-init.md @@ -0,0 +1,22 @@ +``` +Menu = Imp/init_app, __init__ +Title = Imp.init_app, __init__ +``` + +```python +def init_app( + app: Quart, + config: ImpConfig +) -> None: +# -or- +Imp( + app: Quart, + config: ImpConfig +) +``` + +--- + +Initializes the quart app to work with quart-imp. + +See [quart_imp_config-impconfig.md](quart_imp_config-impconfig.html) for more information on the `ImpConfig` class. diff --git a/docs/_md/v1/Imp-init_session.md b/docs/_md/v1/Imp-init_session.md new file mode 100644 index 0000000..6c672c6 --- /dev/null +++ b/docs/_md/v1/Imp-init_session.md @@ -0,0 +1,48 @@ +``` +Menu = Imp/init_session +Title = Imp.init_session +``` + +```python +init_session() -> None +``` + +--- + +Initialize the session variables found in the config. Commonly used in `app.before_request`. + +```python +@app.before_request +async def before_request(): + imp._init_session() +``` + +File: `config.toml` + +```toml +... +[SESSION] +logged_in = false +... +``` + +`logged_in` is now available in the session. + +```python +@app.route('/get-session-value') +async def login(): + print(session['logged_in']) + return "Check Terminal" +``` + +`Output: False` + +Can also be used to reset the values in the session. Here's an example: + +```python +@app.route('/logout') +async def logout(): + session.clear() + imp._init_session() + return redirect(url_for('index')) +``` \ No newline at end of file diff --git a/docs/_md/v1/ImpBlueprint-Introduction.md b/docs/_md/v1/ImpBlueprint-Introduction.md new file mode 100644 index 0000000..7c8247c --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-Introduction.md @@ -0,0 +1,68 @@ +``` +Menu = ImpBlueprint/Introduction +Title = Quart-Imp Blueprint Introduction +``` + +The Quart-Imp Blueprint inherits from the Quart Blueprint class, then adds some additional methods to allow for auto +importing of resources and other nested blueprints. + +The Quart-Imp Blueprint requires you to provide the `ImpBlueprintConfig` class as the second argument to the Blueprint. + +Here's an example of a Quart-Imp Blueprint structure: + +```text +www/ +├── nested_blueprints/ +│ ├── blueprint_one/ +│ │ ├── ... +│ │ └── __init__.py +│ └── blueprint_two/ +│ ├── ... +│ └── __init__.py +├── standalone_nested_blueprint/ +│ ├── ... +│ └── __init__.py +├── routes/ +│ └── index.py +├── static/ +│ └── ... +├── templates/ +│ └── www/ +│ └── index.html +└── __init__.py +``` + +File: `__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + url_prefix="/www", + static_folder="static", + template_folder="templates", + init_session={"logged_in": False}, +)) + +bp.import_resources("routes") +bp.import_nested_blueprints("nested_blueprints") +bp.import_nested_blueprint("standalone_nested_blueprint") +``` + +The `ImpBlueprintConfig` class is used to configure the Blueprint. It provides a little more flexibility than the +standard Quart Blueprint configuration, like the ability to enable or disable the Blueprint. + +`ImpBlueprintConfig`'s `init_session` works the same as `ImpConfig`'s `init_session`, this will add the session data to +the Quart app's session object on initialization of the Quart app. + +To see more about configuration see: [quart_imp.config / ImpBlueprintConfig](quart_imp_config-impblueprintconfig.html) + +`import_resources` method will walk one level deep into the `routes` folder, and import all `.py` files as modules. +For more information see: [ImpBlueprint / import_resources](impblueprint-import_resources.html) + +`import_nested_blueprints` will do the same as `imp.import_blueprints`, but will register the blueprints found as +nested to the current blueprint. For example `www.blueprint_one.index` + +`import_nested_blueprint` behaves the same as `import_nested_blueprints`, but will only import a single blueprint. diff --git a/docs/_md/v1/ImpBlueprint-import_nested_blueprint.md b/docs/_md/v1/ImpBlueprint-import_nested_blueprint.md new file mode 100644 index 0000000..0a69bbc --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-import_nested_blueprint.md @@ -0,0 +1,78 @@ +``` +Menu = ImpBlueprint/import_nested_blueprint +Title = ImpBlueprint.import_nested_blueprint +``` + +```python +import_nested_blueprint(self, blueprint: str) -> None +``` + +--- + +Import a specified Quart-Imp or standard Quart Blueprint relative to the Blueprint root. + +Works the same as [Imp / import_blueprint](imp-import_blueprint.html) but relative to the Blueprint root. + +Blueprints that are imported this way will be scoped to the parent Blueprint that imported them. + +`url_for('my_blueprint.my_nested_blueprint.index')` + +```text +my_blueprint/ +├── routes/... +├── static/... +├── templates/... +│ +├── my_nested_blueprint/ +│ ├── routes/ +│ │ └── index.py +│ ├── static/... +│ ├── templates/... +│ ├── __init__.py +│ +├── __init__.py +``` + +File: `my_blueprint/__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + static_folder="static", + template_folder="templates", +)) + +bp.import_resources("routes") +bp.import_nested_blueprint("my_nested_blueprint") +``` + +File: `my_blueprint/my_nested_blueprint/__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + static_folder="static", + template_folder="templates", +)) + +bp.import_resources("routes") +``` + +File: `my_blueprint/my_nested_blueprint/routes/index.py` + +```python +from quart import render_template + +from .. import bp + + +@bp.route("/") +async def index(): + return await render_template(bp.tmpl("index.html")) +``` \ No newline at end of file diff --git a/docs/_md/v1/ImpBlueprint-import_nested_blueprints.md b/docs/_md/v1/ImpBlueprint-import_nested_blueprints.md new file mode 100644 index 0000000..276938d --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-import_nested_blueprints.md @@ -0,0 +1,60 @@ +``` +Menu = ImpBlueprint/import_nested_blueprints +Title = ImpBlueprint.import_nested_blueprints +``` + +```python +import_nested_blueprints(self, folder: str) -> None +``` + +--- + +Will import all the Blueprints from the given folder relative to the Blueprint's root directory. + +Uses [Blueprint / import_nested_blueprint](blueprint-import_nested_blueprint.html) to import blueprints from +the specified folder. + +Blueprints that are imported this way will be scoped to the parent Blueprint that imported them. + +`url_for('my_blueprint.nested_bp_one.index')` + +`url_for('my_blueprint.nested_bp_two.index')` + +`url_for('my_blueprint.nested_bp_three.index')` + +```text +my_blueprint/ +├── routes/... +├── static/... +├── templates/... +│ +├── nested_blueprints/ +│ │ +│ ├── nested_bp_one/ +│ │ ├── ... +│ │ ├── __init__.py +│ ├── nested_bp_two/ +│ │ ├── ... +│ │ ├── __init__.py +│ └── nested_bp_three/ +│ ├── ... +│ ├── __init__.py +│ +├── __init__.py +``` + +File: `my_blueprint/__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + static_folder="static", + template_folder="templates", +)) + +bp.import_resources("routes") +bp.import_nested_blueprints("nested_blueprints") +``` \ No newline at end of file diff --git a/docs/_md/v1/ImpBlueprint-import_resources.md b/docs/_md/v1/ImpBlueprint-import_resources.md new file mode 100644 index 0000000..65487c7 --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-import_resources.md @@ -0,0 +1,57 @@ +``` +Menu = ImpBlueprint/import_resources +Title = ImpBlueprint.import_resources +``` + +```python +import_resources(folder: str = "routes") -> None +``` + +--- + +Will import all the resources (cli, routes, filters, context_processors...) from the given folder relative to the +Blueprint's root directory. + +```text +my_blueprint +├── user_routes +│ ├── user_dashboard.py +│ └── user_settings.py +├── car_routes +│ ├── car_dashboard.py +│ └── car_settings.py +├── static/... +├── templates/ +│ └── my_blueprint/ +│ ├── user_dashboard.html +│ └── ... +├── __init__.py +``` + +File: `my_blueprint/__init__.py` + +```python +from quart_imp import ImpBlueprint +from quart_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + static_folder="static", + template_folder="templates", +)) + +bp.import_resources("user_routes") +bp.import_resources("car_routes") +``` + +File: `my_blueprint/user_routes/user_dashboard.py` + +```python +from quart import render_template + +from .. import bp + +@bp.route("/user-dashboard") +async def user_dashboard(): + return await render_template(bp.tmpl("user_dashboard.html")) +``` diff --git a/docs/_md/v1/ImpBlueprint-init.md b/docs/_md/v1/ImpBlueprint-init.md new file mode 100644 index 0000000..d5cb266 --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-init.md @@ -0,0 +1,17 @@ +``` +Menu = ImpBlueprint/__init__ +Title = Quart-Imp Blueprint __init__ +``` + +```python +ImpBlueprint(dunder_name: str, config: ImpBlueprintConfig) -> None +``` + +--- + +Initializes the Quart-Imp Blueprint. + +`dunder_name` should always be set to `__name__` + +`config` is an instance of `ImpBlueprintConfig` that will be used to load the Blueprint's configuration. +See [quart_imp.config / ImpBlueprintConfig](quart_imp_config-impblueprintconfig.html) for more information. diff --git a/docs/_md/v1/ImpBlueprint-tmpl.md b/docs/_md/v1/ImpBlueprint-tmpl.md new file mode 100644 index 0000000..d3e94d6 --- /dev/null +++ b/docs/_md/v1/ImpBlueprint-tmpl.md @@ -0,0 +1,44 @@ +``` +Menu = ImpBlueprint/tmpl +Title = ImpBlueprint.tmpl +``` + +```python +tmpl(template: str) -> str +``` + +--- + +Scopes the template lookup to the name of the blueprint (this takes from the `__name__` attribute of the Blueprint). + +Due to the way Quart templating works, and to avoid template name collisions. +It is standard practice to place the name of the Blueprint in the template path, +then to place any templates under that folder. + +```text +my_blueprint/ +├── routes/ +│ └── index.py +├── static/... +│ +├── templates/ +│ └── my_blueprint/ +│ └── index.html +│ +├── __init__.py +``` + +File: `my_blueprint/routes/index.py` + +```python +from quart import render_template + +from .. import bp + + +@bp.route("/") +async def index(): + return await render_template(bp.tmpl("index.html")) +``` + +`bp.tmpl("index.html")` will output `"my_blueprint/index.html"`. diff --git a/docs/_md/v1/__index__.md b/docs/_md/v1/__index__.md new file mode 100644 index 0000000..7f496f5 --- /dev/null +++ b/docs/_md/v1/__index__.md @@ -0,0 +1,121 @@ +# Welcome to the Quart-Imp Documentation + +## What is Quart-Imp? + +Quart-Imp's main purpose is to help simplify the importing of blueprints, and resources. It has a few extra +features built in to help with securing pages and password authentication. + +## Install Quart-Imp + +```bash +pip install quart-imp +``` + +## Getting Started + +To get started right away, you can use the CLI commands to create a new Quart-Imp project. + +```bash +quart-imp init +``` + +### Minimal Quart-Imp Setup + +Run the following command to create a minimal Quart-Imp project. + +```bash +quart-imp init -n app --minimal +``` + +See [CLI Commands / quart-imp init](cli_commands-quart-imp_init.html) for more information. + +### The minimal structure + +#### Folder Structure + +```text +app/ +├── resources/ +│ ├── static/... +│ ├── templates/ +│ │ └── index.html +│ └── index.py +└── __init__.py +``` + +File: `app/__init__.py` + +```python +from quart import Quart + +from quart_imp import Imp +from quart_imp.config import QuartConfig, ImpConfig + +imp = Imp() + + +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() + # Takes argument 'folder' default folder is 'resources' + + return app +``` + +File: `app/resources/routes.py` + +```python +from quart import current_app as app +from quart import render_template + + +@app.route("/") +async def index(): + return await render_template("index.html") +``` + +File: `app/resources/templates/index.html` + +```html + + +
+ +" + mistune.escape(code) + ""
diff --git a/docs/_templates/__main__.html b/docs/_templates/__main__.html
new file mode 100644
index 0000000..d9a26de
--- /dev/null
+++ b/docs/_templates/__main__.html
@@ -0,0 +1,26 @@
+
+
+
+
+ Quart-Imp has its own type of blueprint. It comes with some methods to auto import routes, and nested blueprints etc... +see ImpBlueprint / Introduction for more information.
+You have the option to generate a regular template rendering blueprint, or a API blueprint that returns a JSON response.
+quart-imp blueprint --help
+or
+quart-imp api-blueprint --help
+To generate a Quart-Imp blueprint, run the following command:
+quart-imp blueprint
+or
+quart-imp api-blueprint
+After running this command, you will be prompted to enter the location of where you want to create your blueprint:
+~ $ quart-imp blueprint
+(Creation is relative to the current working directory)
+Folder to create blueprint in [Current Working Directory]:
+As detailed in the prompt, the creation of the blueprint is relative to the current working directory. So to create a
+blueprint in the folder app/blueprints, you would enter app/blueprints in the prompt.
~ $ quart-imp blueprint
+(Creation is relative to the current working directory)
+Folder to create blueprint in [Current Working Directory]: app/blueprints
+You will then be prompted to enter a name for your blueprint:
+~ $ quart-imp blueprint
+...
+Name of the blueprint to create [my_new_blueprint]:
+The default name is 'my_new_blueprint', we will change this to 'admin'
+~ $ quart-imp blueprint
+...
+Name of the blueprint to create [my_new_blueprint]: admin
+After creating your blueprint, the folder structure will look like this:
+app/
+├── blueprints
+│ └── admin
+│ ├── routes
+│ │ └── index.py
+│ │
+│ ├── static
+│ │ ├── css
+│ │ │ └── water.css
+│ │ ├── img
+│ │ │ └── quart-imp-logo.png
+│ │ └── js
+│ │ └── main.js
+│ │
+│ ├── templates
+│ │ └── www
+│ │ ├── extends
+│ │ │ └── main.html
+│ │ ├── includes
+│ │ │ ├── footer.html
+│ │ │ └── header.html
+│ │ └── index.html
+│ │
+│ └── __init__.py
+│
+...
+This is a self-contained blueprint, so it has its own static, templates and routes folders. +You can now navigate '/admin'
+You can streamline this process by specifying the name of the blueprint, the folder to +create it in and the configuration to use, like so:
+quart-imp blueprint -n admin -f app/blueprints
+Quart-Imp has a cli command that deploys a new ready-to-go project. +This project is structured in a way to give you the best idea of +how to use Quart-Imp.
+quart-imp init --help
+Make sure you are in the virtual environment, and at the root of your +project folder, then run the following command:
+quart-imp init
+After running this command, you will be prompted to choose what type of +app you want to deploy:
+~ $ quart-imp init
+What type of app would you like to create? (minimal, slim, full) [minimal]:
+See below for the differences between the app types.
+After this, you will be prompted to enter a name for your app:
+~ $ quart-imp init
+...
+What would you like to call your app? [app]:
+'app' is the default name, so if you just press enter, your app will be +called 'app'. You will then see this output:
+~ FILES CREATED WILL LOOP OUT HERE ~
+
+===================
+Quart app deployed!
+===================
+
+Your app has the default name of 'app'
+Quart will automatically look for this!
+Run: quart run --debug
+If you called your app something other than 'app', like 'new' for example, you will see:
+~ FILES CREATED WILL LOOP OUT HERE ~
+
+===================
+Quart app deployed!
+===================
+
+Your app has the name of 'new'
+Run: quart --app new run --debug
+As you can see from the output, it gives you instructions on how to start your app, +depending on the name you gave it.
+You should see a new folder that has been given the name you specified in
+the quart-imp init command.
You can also specify a name for your app in the command itself, like so:
+quart-imp init -n my_app
+This will create a new app called 'my_app'. +The default will be a minimal app, this has no blueprints.
+You can also deploy a slim app, that will have one blueprint, like so:
+quart-imp init -n my_app --slim
+You can also deploy a full app that is setup for multiple blueprints, like so:
+quart-imp init -n my_app --full
+quart-imp init --minimal:
app/
+├── resources
+│ ├── static
+│ │ ├── css
+│ │ │ └── water.css
+│ │ ├── img
+│ │ │ └── quart-imp-logo.png
+│ │ └── favicon.ico
+│ ├── templates
+│ │ └── index.html
+│ └── routes.py
+│
+└── __init__.py
+quart-imp init --slim:
app/
+├── extensions
+│ └── __init__.py
+│
+├── resources
+│ ├── cli
+│ │ └── cli.py
+│ ├── error_handlers
+│ │ └── error_handlers.py
+│ ├── static
+│ │ ├── css
+│ │ │ └── water.css
+│ │ ├── img
+│ │ │ └── quart-imp-logo.png
+│ │ └── favicon.ico
+│ └── templates
+│ └── error.html
+│
+├── www
+│ ├── __init__.py
+│ ├── routes
+│ │ └── index.py
+│ ├── static
+│ │ ├── css
+│ │ │ └── water.css
+│ │ ├── img
+│ │ │ └── quart-imp-logo.png
+│ │ └── js
+│ │ └── main.js
+│ └── templates
+│ └── www
+│ ├── extends
+│ │ └── main.html
+│ ├── includes
+│ │ ├── footer.html
+│ │ └── header.html
+│ └── index.html
+│
+└── __init__.py
+quart-imp init --full:
app/
+├── blueprints
+│ └── www
+│ ├── __init__.py
+│ ├── routes
+│ │ └── index.py
+│ ├── static
+│ │ ├── css
+│ │ │ └── water.css
+│ │ ├── img
+│ │ │ └── quart-imp-logo.png
+│ │ └── js
+│ │ └── main.js
+│ └── templates
+│ └── www
+│ ├── extends
+│ │ └── main.html
+│ ├── includes
+│ │ ├── footer.html
+│ │ └── header.html
+│ └── index.html
+│
+├── extensions
+│ └── __init__.py
+│
+├── resources
+│ ├── cli
+│ │ └── cli.py
+│ ├── context_processors
+│ │ └── context_processors.py
+│ ├── error_handlers
+│ │ └── error_handlers.py
+│ ├── filters
+│ │ └── filters.py
+│ ├── routes
+│ │ └── routes.py
+│ ├── static
+│ │ └── favicon.ico
+│ └── templates
+│ └── error.html
+│
+└── __init__.py
+import_app_resources(
+ folder: str = "resources",
+ factories: Optional[List] = None,
+ static_folder: str = "static",
+ templates_folder: str = "templates",
+ files_to_import: Optional[List] = None,
+ folders_to_import: Optional[List] = None,
+ ) -> None
+Import standard app resources from the specified folder.
+This will import any resources that have been set to the Quart app.
+Routes, context processors, cli, etc.
+Can only be called once.
+If no static and or template folder is found, the static and or template folder will be set to None in the Quart app +config.
+imp.import_app_resources(folder="resources")
+# or
+imp.import_app_resources()
+# as the default folder is "resources"
+Folder Structure: resources
app
+├── resources
+│ ├── routes.py
+│ ├── app_fac.py
+│ ├── static
+│ │ └── css
+│ │ └── style.css
+│ └── templates
+│ └── index.html
+└── ...
+...
+File: routes.py
from quart import current_app as app
+from quart import render_template
+
+
+@app.route("/")
+async def index():
+ return await render_template("index.html")
+Factories are functions that are called when importing the app resources. Here's an example:
+imp.import_app_resources(
+ folder="resources",
+ factories=["development_cli"]
+)
+["development_cli"] => development_cli(app) function will be called, and the current app will be passed in.
File: app_fac.py
def development_cli(app):
+ @app.cli.command("dev")
+ def dev():
+ print("dev cli command")
+By default, all files and folders will be imported.
+To disable this, set files_to_import and or
+folders_to_import to [None].
imp.import_app_resources(scope_import=[None], folders_to_import=[None])
+To scope the imports, set the files_to_import and or folders_to_import to a list of files and or folders.
files_to_import=["cli.py", "routes.py"] => will only import the files resources/cli.py
+and resources/routes.py
folders_to_import=["template_filters", "context_processors"] => will import all files in the folders
+resources/template_filters/*.py and resources/context_processors/*.py
import_blueprint(self, blueprint: str) -> None
+Import a specified Quart-Imp or standard Quart Blueprint relative to the Quart app root.
+app
+├── my_blueprint
+│ ├── ...
+│ └── __init__.py
+├── ...
+└── __init__.py
+File: app/__init__.py
from quart import Quart
+
+from quart_imp import Imp
+
+imp = Imp()
+
+
+def create_app():
+ app = Quart(__name__)
+ imp.init_app(app)
+
+ imp.import_blueprint("my_blueprint")
+
+ return app
+Quart-Imp Blueprints have the ability to auto import resources, and initialize session variables.
+For more information on how Quart-Imp Blueprints work, see the ImpBlueprint / Introduction
+app
+├── my_blueprint
+│ ├── routes
+│ │ └── index.py
+│ ├── static
+│ │ └── css
+│ │ └── style.css
+│ ├── templates
+│ │ └── my_blueprint
+│ │ └── index.html
+│ ├── __init__.py
+│ └── config.toml
+└── ...
+File: __init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(
+ __name__,
+ ImpBlueprintConfig(
+ enabled=True,
+ url_prefix="/my-blueprint",
+ static_folder="static",
+ template_folder="templates",
+ static_url_path="/static/my_blueprint",
+ init_session={"my_blueprint": "session_value"},
+ ),
+)
+
+bp.import_resources("routes")
+File: routes / index.py
from .. import bp
+
+
+@bp.route("/")
+async def index():
+ return "regular_blueprint"
+app
+├── my_blueprint
+│ ├── ...
+│ └── __init__.py
+└── ...
+File: __init__.py
from quart import Blueprint
+
+bp = Blueprint("my_blueprint", __name__, url_prefix="/my-blueprint")
+
+
+@bp.route("/")
+async def index():
+ return "regular_blueprint"
+Both of the above examples will work with imp.import_blueprint("my_blueprint"), they will be registered
+with the Quart app, and will be accessible via url_for("my_blueprint.index").
import_blueprints(self, folder: str) -> None
+Import all Quart-Imp or standard Quart Blueprints from a specified folder relative to the Quart app root.
+app/
+├── blueprints/
+│ ├── admin/
+│ │ ├── ...
+│ │ └── __init__.py
+│ ├── www/
+│ │ ├── ...
+│ │ └── __init__.py
+│ └── api/
+│ ├── ...
+│ └── __init__.py
+├── ...
+└── __init__.py
+File: app/__init__.py
from quart import Quart
+
+from quart_imp import Imp
+
+imp = Imp()
+
+
+def create_app():
+ app = Quart(__name__)
+ imp.init_app(app)
+
+ imp.import_blueprints("blueprints")
+
+ return app
+This will import all Blueprints from the blueprints folder using the Imp.import_blueprint method.
+See Imp / import_blueprint for more information.
def init_app(
+ app: Quart,
+ config: ImpConfig
+) -> None:
+# -or-
+Imp(
+ app: Quart,
+ config: ImpConfig
+)
+Initializes the quart app to work with quart-imp.
+See quart_imp_config-impconfig.md for more information on the ImpConfig class.
init_session() -> None
+Initialize the session variables found in the config. Commonly used in app.before_request.
@app.before_request
+async def before_request():
+ imp._init_session()
+File: config.toml
...
+[SESSION]
+logged_in = false
+...
+logged_in is now available in the session.
@app.route('/get-session-value')
+async def login():
+ print(session['logged_in'])
+ return "Check Terminal"
+Output: False
Can also be used to reset the values in the session. Here's an example:
+@app.route('/logout')
+async def logout():
+ session.clear()
+ imp._init_session()
+ return redirect(url_for('index'))
+Quart-Imp is a Quart extension that provides auto import methods for various Quart resources. It will import +blueprints, and other resources. It uses the importlib module to achieve this.
+Quart-Imp favors the application factory pattern as a project structure, and is opinionated towards using +Blueprints. However, you can use Quart-Imp without using Blueprints.
+Here's an example of a standard Quart-Imp project structure:
+app/
+├── blueprints/
+│ ├── admin/...
+│ ├── api/...
+│ └── www/...
+├── resources/
+│ ├── filters/...
+│ ├── context_processors/...
+│ ├── static/...
+│ └── templates/...
+└── __init__.py
+Here's an example of the app/__init__.py file:
from quart import Quart
+from quart_sqlalchemy import SQLAlchemy
+from quart_imp import Imp
+from quart_imp.config import QuartConfig, ImpConfig
+
+db = SQLAlchemy()
+imp = Imp()
+
+
+def create_app():
+ app = Quart(__name__)
+ QuartConfig(
+ secret_key="super_secret_key",
+ app_instance=app,
+ )
+
+ imp.init_app(app, config=ImpConfig(
+ init_session={"logged_in": False},
+ ))
+ imp.import_app_resources("resources")
+ imp.import_blueprints("blueprints")
+
+ db.init_app(app)
+
+ return app
+The Quart configuration can be loaded from any standard Quart configuration method, or from the QuartConfig class
+shown above.
This class contains the standard Quart configuration options found in the Quart documentation.
+The ImpConfig class is used to configure the Imp instance.
The init_session option of the ImpConfig class is used to set the initial session variables for the Quart app.
+This happens before the request is processed.
ImpConfig also has the ability to set SQLALCHEMY_DATABASE_URI and SQLALCHEMY_BINDS
For more information about the configuration setting see +quart_imp_config-impconfig.md.
+import_app_resources will walk one level deep into the resources folder, and import
+all .py files as modules.
+It will also check for the existence of a static and templates folder, and register them with the Quart app.
There is a couple of options for import_app_resources to control what
+is imported, see: Imp / import_app_resources
import_blueprints expects a folder that contains many Blueprint as Python packages.
+It will check each blueprint folder's __init__.py file for an instance of a Quart Blueprint or a
+Quart-Imp Blueprint. That instant will then be registered with the Quart app.
See more about how importing blueprints work here: ImpBlueprint / Introduction
+ +import_nested_blueprint(self, blueprint: str) -> None
+Import a specified Quart-Imp or standard Quart Blueprint relative to the Blueprint root.
+Works the same as Imp / import_blueprint but relative to the Blueprint root.
+Blueprints that are imported this way will be scoped to the parent Blueprint that imported them.
+url_for('my_blueprint.my_nested_blueprint.index')
my_blueprint/
+├── routes/...
+├── static/...
+├── templates/...
+│
+├── my_nested_blueprint/
+│ ├── routes/
+│ │ └── index.py
+│ ├── static/...
+│ ├── templates/...
+│ ├── __init__.py
+│
+├── __init__.py
+File: my_blueprint/__init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(__name__, ImpBlueprintConfig(
+ enabled=True,
+ static_folder="static",
+ template_folder="templates",
+))
+
+bp.import_resources("routes")
+bp.import_nested_blueprint("my_nested_blueprint")
+File: my_blueprint/my_nested_blueprint/__init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(__name__, ImpBlueprintConfig(
+ enabled=True,
+ static_folder="static",
+ template_folder="templates",
+))
+
+bp.import_resources("routes")
+File: my_blueprint/my_nested_blueprint/routes/index.py
from quart import render_template
+
+from .. import bp
+
+
+@bp.route("/")
+async def index():
+ return await render_template(bp.tmpl("index.html"))
+import_nested_blueprints(self, folder: str) -> None
+Will import all the Blueprints from the given folder relative to the Blueprint's root directory.
+Uses Blueprint / import_nested_blueprint to import blueprints from +the specified folder.
+Blueprints that are imported this way will be scoped to the parent Blueprint that imported them.
+url_for('my_blueprint.nested_bp_one.index')
url_for('my_blueprint.nested_bp_two.index')
url_for('my_blueprint.nested_bp_three.index')
my_blueprint/
+├── routes/...
+├── static/...
+├── templates/...
+│
+├── nested_blueprints/
+│ │
+│ ├── nested_bp_one/
+│ │ ├── ...
+│ │ ├── __init__.py
+│ ├── nested_bp_two/
+│ │ ├── ...
+│ │ ├── __init__.py
+│ └── nested_bp_three/
+│ ├── ...
+│ ├── __init__.py
+│
+├── __init__.py
+File: my_blueprint/__init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(__name__, ImpBlueprintConfig(
+ enabled=True,
+ static_folder="static",
+ template_folder="templates",
+))
+
+bp.import_resources("routes")
+bp.import_nested_blueprints("nested_blueprints")
+import_resources(folder: str = "routes") -> None
+Will import all the resources (cli, routes, filters, context_processors...) from the given folder relative to the +Blueprint's root directory.
+my_blueprint
+├── user_routes
+│ ├── user_dashboard.py
+│ └── user_settings.py
+├── car_routes
+│ ├── car_dashboard.py
+│ └── car_settings.py
+├── static/...
+├── templates/
+│ └── my_blueprint/
+│ ├── user_dashboard.html
+│ └── ...
+├── __init__.py
+File: my_blueprint/__init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(__name__, ImpBlueprintConfig(
+ enabled=True,
+ static_folder="static",
+ template_folder="templates",
+))
+
+bp.import_resources("user_routes")
+bp.import_resources("car_routes")
+File: my_blueprint/user_routes/user_dashboard.py
from quart import render_template
+
+from .. import bp
+
+@bp.route("/user-dashboard")
+async def user_dashboard():
+ return await render_template(bp.tmpl("user_dashboard.html"))
+ImpBlueprint(dunder_name: str, config: ImpBlueprintConfig) -> None
+Initializes the Quart-Imp Blueprint.
+dunder_name should always be set to __name__
config is an instance of ImpBlueprintConfig that will be used to load the Blueprint's configuration.
+See quart_imp.config / ImpBlueprintConfig for more information.
The Quart-Imp Blueprint inherits from the Quart Blueprint class, then adds some additional methods to allow for auto +importing of resources and other nested blueprints.
+The Quart-Imp Blueprint requires you to provide the ImpBlueprintConfig class as the second argument to the Blueprint.
Here's an example of a Quart-Imp Blueprint structure:
+www/
+├── nested_blueprints/
+│ ├── blueprint_one/
+│ │ ├── ...
+│ │ └── __init__.py
+│ └── blueprint_two/
+│ ├── ...
+│ └── __init__.py
+├── standalone_nested_blueprint/
+│ ├── ...
+│ └── __init__.py
+├── routes/
+│ └── index.py
+├── static/
+│ └── ...
+├── templates/
+│ └── www/
+│ └── index.html
+└── __init__.py
+File: __init__.py
from quart_imp import ImpBlueprint
+from quart_imp.config import ImpBlueprintConfig
+
+bp = ImpBlueprint(__name__, ImpBlueprintConfig(
+ enabled=True,
+ url_prefix="/www",
+ static_folder="static",
+ template_folder="templates",
+ init_session={"logged_in": False},
+))
+
+bp.import_resources("routes")
+bp.import_nested_blueprints("nested_blueprints")
+bp.import_nested_blueprint("standalone_nested_blueprint")
+The ImpBlueprintConfig class is used to configure the Blueprint. It provides a little more flexibility than the
+standard Quart Blueprint configuration, like the ability to enable or disable the Blueprint.
ImpBlueprintConfig's init_session works the same as ImpConfig's init_session, this will add the session data to
+the Quart app's session object on initialization of the Quart app.
To see more about configuration see: quart_imp.config / ImpBlueprintConfig
+import_resources method will walk one level deep into the routes folder, and import all .py files as modules.
+For more information see: ImpBlueprint / import_resources
import_nested_blueprints will do the same as imp.import_blueprints, but will register the blueprints found as
+nested to the current blueprint. For example www.blueprint_one.index
import_nested_blueprint behaves the same as import_nested_blueprints, but will only import a single blueprint.
tmpl(template: str) -> str
+Scopes the template lookup to the name of the blueprint (this takes from the __name__ attribute of the Blueprint).
Due to the way Quart templating works, and to avoid template name collisions. +It is standard practice to place the name of the Blueprint in the template path, +then to place any templates under that folder.
+my_blueprint/
+├── routes/
+│ └── index.py
+├── static/...
+│
+├── templates/
+│ └── my_blueprint/
+│ └── index.html
+│
+├── __init__.py
+File: my_blueprint/routes/index.py
from quart import render_template
+
+from .. import bp
+
+
+@bp.route("/")
+async def index():
+ return await render_template(bp.tmpl("index.html"))
+bp.tmpl("index.html") will output "my_blueprint/index.html".
Quart-Imp's main purpose is to help simplify the importing of blueprints, and resources. It has a few extra +features built in to help with securing pages and password authentication.
+pip install quart-imp
+To get started right away, you can use the CLI commands to create a new Quart-Imp project.
+quart-imp init
+Run the following command to create a minimal Quart-Imp project.
+quart-imp init -n app --minimal
+See CLI Commands / quart-imp init for more information.
+app/
+├── resources/
+│ ├── static/...
+│ ├── templates/
+│ │ └── index.html
+│ └── index.py
+└── __init__.py
+File: app/__init__.py
from quart import Quart
+
+from quart_imp import Imp
+from quart_imp.config import QuartConfig, ImpConfig
+
+imp = Imp()
+
+
+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()
+ # Takes argument 'folder' default folder is 'resources'
+
+ return app
+File: app/resources/routes.py
from quart import current_app as app
+from quart import render_template
+
+
+@app.route("/")
+async def index():
+ return await render_template("index.html")
+File: app/resources/templates/index.html
<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Quart-Imp</title>
+</head>
+<body>
+<h1>Quart-Imp</h1>
+</body>
+</html>
+Setting up a virtual environment is recommended.
+Linux / Darwin
+python3 -m venv venv
+source venv/bin/activate
+Windows
+python -m venv venv
+.\venv\Scripts\activate
+from quart_imp.auth import authenticate_password
+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
+For use in password hashing.
+To be used alongside the quart_imp.auth / encrypt_password function.
+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.
+Note:
+Plain password: "password"
+Generated salt: "^%$*" (randomly generated)
+Generated pepper (length 1): "A" (randomly generated)
+Pepper position: "end"
+input_password = "password"
+database_password = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0..." # pulled from database
+database_salt = "^%$*" # pulled from database
+
+authenticate_password(
+ input_password,
+ database_password,
+ database_salt
+) # >>> True
+from quart_imp.auth import encrypt_password
+encrypt_password(
+ password: str,
+ salt: str,
+ encryption_level: int = 512,
+ pepper_length: int = 1,
+ pepper_position: t.Literal["start", "end"] = "end"
+) -> str
+For use in password hashing.
+To be used alongside the quart_imp.auth / authenticate_password function.
+Takes the plain password, applies a pepper, salts it, then produces a digested sha512 or sha256 if specified.
+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".
+Note:
+Plain password: "password"
+Generated salt: "^%$*" (randomly generated)
+Generated pepper (length 1): "A" (randomly generated)
+Pepper position: "end"
+from quart_imp.auth import generate_alphanumeric_validator
+generate_alphanumeric_validator(length: int = 8) -> str
+Generates a random alphanumeric string of the given length.
+(letters are capitalized)
+generate_alphanumeric_validator(8) # >>> 'A1B2C3D4'
+from quart_imp.auth import generate_csrf_token
+generate_csrf_token() -> str
+Generates a SHA1 using the current date and time.
+For use in Cross-Site Request Forgery.
+Also used by the quart_imp.security / csrf_protect decorator.
+generate_csrf_token() # >>> 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0'
+from quart_imp.auth import generate_email_validator
+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.
See quart_imp.auth / generate_alphanumeric_validator +for more information.
+generate_email_validator() # >>> 'A1B2C3D4'
+from quart_imp.auth import generate_numeric_validator
+generate_numeric_validator(length: int) -> int
+Generates random choice between 1 * (length) and 9 * (length).
+If the length is 4, it will generate a number between 1111 and 9999.
+For use in MFA email, or unique filename generation.
+generate_numeric_validator(4) # >>> 1234
+from quart_imp.auth import generate_password
+generate_password(style: str = "mixed", length: int = 3) -> str
+Generates a password of (length) characters.
+The Default length is 3.
+Style options: "animals", "colors", "mixed" - defaults to "mixed"
+generate_password(style="animals", length=3) # >>> 'Cat-Goat-Pig12'
+from quart_imp.auth import generate_private_key
+generate_private_key(hook: t.Optional[str]) -> str
+Generates a sha256 private key from a passed in hook value.
+If no hook is passed in, it will generate a hook using datetime.now() and a +random number between 1 and 1000.
+@app.route('/register', methods=['GET', 'POST'])
+async def register():
+ if request.method == "POST":
+ ...
+ salt = generate_salt()
+ password = request.form.get('password')
+ encrypted_password = encrypt_password(password, salt)
+ ...
+ user = User(
+ username=username,
+ email=email,
+ password=encrypted_password,
+ salt=salt,
+ private_key=generate_private_key(hook=username)
+ )
+ ...
+from quart_imp.auth import generate_salt
+generate_salt(length: int = 4) -> str
+Generates a string of (length) characters of punctuation.
+The Default length is 4.
+For use in password hashing and storage of passwords in the database.
+generate_salt() # >>> '*!$%'
+@app.route('/register', methods=['GET', 'POST'])
+async def register():
+ if request.method == "POST":
+ ...
+ salt = generate_salt()
+ password = request.form.get('password')
+ encrypted_password = encrypt_password(password, salt)
+ ...
+
+ user = User(
+ username=username,
+ email=email,
+ password=encrypted_password,
+ salt=salt
+ )
+ ...
+from quart_imp.auth import is_email_address_valid
+is_email_address_valid(
+ email_address: str
+) -> bool
+Checks if an email address is valid.
+Is not completely RFC 5322 compliant, but it is good enough for most use cases.
+Here are examples of mistakes that it will not catch:
+email@[123.123.123.123]
+“email”@example.com
+very.unusual.“@”.unusual.com@example.com
+very.“(),:;<>[]”.VERY.“very@\\ "very”.unusual@strange.example.com
+email@example.com (Joe Smith)
+email@111.222.333.44444
+is_email_address_valid('hello@example.com') # >>> True
+
+is_email_address_valid('hello@hello@example.com') # >>> False
+from quart_imp.auth import is_username_valid
+is_username_valid(
+ username: str,
+ allowed: t.Optional[t.List[t.Literal["all", "dot", "dash", "under"]]] = None
+) -> bool
+Checks if a username is valid.
+Valid usernames can only include letters, +numbers, ., -, and _ but cannot begin or end with +the last three mentioned.
+is_username_valid("username", allowed=["all"])
+Output:
+username : WILL PASS : True
+user.name : WILL PASS : True
+user-name : WILL PASS : True
+user_name : WILL PASS : True
+_user_name : WILL PASS : False
+is_username_valid("username", allowed=["dot", "dash"])
+Output:
+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
+from quart_imp.config import ImpBlueprintConfig
+ImpBlueprintConfig(
+ enabled: bool = False,
+ url_prefix: str = None,
+ subdomain: str = None,
+ url_defaults: dict = None,
+ static_folder: t.Optional[str] = None,
+ template_folder: t.Optional[str] = None,
+ static_url_path: t.Optional[str] = None,
+ root_path: str = None,
+ cli_group: str = None,
+ init_session: dict = None
+)
+A class that holds a Quart-Imp blueprint configuration.
+Most of these values are passed to the Blueprint class when the blueprint is registered.
The enabled argument is used to enable or disable the blueprint. This is useful for feature flags.
init_session is used to set the session values in the main before_request function.
from quart_imp.config import ImpConfig
+ImpConfig(
+ init_session: t.Optional[t.Dict[str, t.Any]] = None,
+)
+The ImpConfig class is used to set the initial session data
+that the application will use.
imp_config = ImpConfig(
+ init_session={"key": "value"},
+)
+
+
+def create_app():
+ app = Quart(__name__)
+ QuartConfig(debug=True, app_instance=app)
+ imp.init_app(app, imp_config)
+ ...
+from quart_imp.config import QuartConfig
+QuartConfig(
+ 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
+)
+A class that holds a Quart configuration values.
+You can set the configuration values to the app instance by either passing the app instance to the app_instance
+parameter or by calling the apply_config method on the QuartConfig instance.
def create_app():
+ app = Quart(__name__)
+ QuartConfig(debug=True, app_instance=app)
+ return app
+or
+def create_app():
+ app = Quart(__name__)
+ config = QuartConfig(debug=True)
+ config.apply_config(app)
+ return app
+from quart_imp.security import api_login_check
+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
+)
+@api_login_check(...)
A decorator that is used to secure API routes that return JSON responses.
+session_key The session key to check for.
values_allowed A list of or singular value(s) that the session key must contain.
fail_json JSON that is returned on failure. {"error": "You are not logged in."} by default.
@bp.route("/api/resource", methods=["GET"])
+@api_login_check('logged_in', True)
+async def api_page():
+ ...
+@bp.route("/api/resource", methods=["GET"])
+@api_login_check('logged_in', True, fail_json={"failed": "You need to be logged in."})
+async def api_page():
+ ...
+from quart_imp.security import include_csrf
+include_csrf(
+ session_key: str = "csrf",
+ form_key: str = "csrf",
+ abort_code: int = 401
+)
+@include_csrf(...)
A decorator that handles CSRF protection.
+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.
+@bp.route("/admin", methods=["GET", "POST"])
+@include_csrf(session_key="csrf", form_key="csrf")
+async 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 await render_template("admin.html", csrf=session.get("csrf"))
+Form key:
+<input type="hidden" name="csrf" value="{{ csrf }}">
+from quart_imp.security import login_check
+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"
+)
+@login_check(...)
A decorator that checks if the specified session key exists and contains the specified value.
+session_key The session key to check for.
values_allowed A list of or singular value(s) that the session key must contain.
fail_endpoint The endpoint to redirect to if the session key does not exist or does not contain the specified values.
endpoint_kwargs A dictionary of keyword arguments to pass to the redirect endpoint.
message If a message is specified, a flash message is shown.
message_category The category of the flash message.
@bp.route("/admin", methods=["GET"])
+@login_check(
+ 'logged_in',
+ True,
+ fail_endpoint='blueprint.login_page',
+ message="Login needed"
+)
+async def admin_page():
+ ...
+@bp.route("/login-page", methods=["GET"])
+@login_check(
+ 'logged_in',
+ True,
+ pass_endpoint='blueprint.admin_page',
+ message="Already logged in"
+)
+async def login_page():
+ ...
+from quart_imp.security import pass_function_check
+def pass_function_check(
+ function: t.Callable,
+ predefined_args: t.Optional[t.Dict] = 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,
+)
+NOTE: This was added mostly as an experimental feature, but ended up being useful in some cases.
+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).
Example:
+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"
+)
+async 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"
+)
+async def admin_page_overwrite():
+ ...
+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().
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
+)
+async def admin_page_overwrite_with_session():
+ ...
+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.
+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'.
+ +from quart_imp.security import permission_check
+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"
+)
+@permission_check(...)
A decorator that checks if the specified session key exists and its value(s) match the specified value(s).
+session_key The session key to check for.
values_allowed A list of or singular value(s) that the session key must contain.
fail_endpoint The endpoint to redirect to if the session key does not exist or does not contain the specified values.
endpoint_kwargs A dictionary of keyword arguments to pass to the redirect endpoint.
message If a message is specified, a flash message is shown.
message_category The category of the flash message.
@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"
+)
+async def admin_page():
+ ...
+