Skip to content

Feature: Allow custom folder locations (and thoughts on how to solve it by refactoring a lot of how py4web handles apps) #947

Open
@laundmo

Description

@laundmo

static

In _scaffold and a few other example apps, the static folder is defined in settings.py like this:

STATIC_FOLDER = required_folder(APP_FOLDER, "static")

This setting is never used anywhere, and is completely skipped by the fact Reloader.import_app has a static path of <appname>/static/<path>, hardcoded here:

py4web/py4web/core.py

Lines 1445 to 1450 in e9f28a5

static_folder = os.path.join(path, "static")
if os.path.exists(static_folder):
app_name = path.split(os.path.sep)[-1]
prefix = "" if app_name == "_default" else f"/{app_name}"
path = prefix + r"/static/<re((_\d+(\.\d+){2}/)?)><fp.path()>"

templates

Theres no settings.py setting for templates yet. The template path is hardcoded by a combination of the Template.on_success method:

py4web/py4web/core.py

Lines 619 to 624 in e9f28a5

app_folder = os.path.join(os.environ["PY4WEB_APPS_FOLDER"], request.app_name)
path = self.path or os.path.join(app_folder, "templates")
filename = os.path.join(path, self.filename)
if not os.path.exists(filename):
generic_filename = os.path.join(path, "generic.html")
if os.path.exists(generic_filename):

And the fact instances of Template are created based on a str in action.uses:
if isinstance(fixture, str):

This means using the normal way of adding templates, by using a str as an argument to action.uses, it's impossible to set the Template.path attribute. To change the Template base path, a user would have to explicitly create Template instances for each individual controller.

issues

Currently, py4web only seems to separate multiple apps by their routes in Reloader. Any global values py4web exposes are either utilizing thread locals scoped to the request, or are potentially shared between all apps. This means a simple global variable which the user can overwrite would change this variable for every app.

solution

One possible solution, which I quite like, would be a App class which owns app-specific configuration. For new apps, this would be instantiated in common.py and passed the relevant information like static path, template path, and possibly other per-app config.

py4web could store instances of App by app_name, where each App instance stores its own routes, error handlers, and possibly other related data (ICECUPE? a bottle application object?).

action could then be a method or property of App. For easier migration, users could set action = myapp.action in common.py and import this instead of py4web.action in controllers.

The App object could also define methods for Reloader to use, like App.unload() which removes this instances routes and maybe even unloads the modules, and App.load() which implements some of what Reloader.import_app() currently does, like registering the static route (this is one way the static path could be read from the App instance)
Doing reloading like this could allow for easily implementing a pre_unload callback, for apps to do their own clean-up before they're reloaded.

To maintain backwards compatibility without any user interaction, py4web could define action using a (subclass) of App containing the current default logic, then expose the normal action with action = default_app.action.

A App class like this with an instance per App, would also allow easily defining other utilities like global Template helpers which are currently made more difficult by the fact theres no way to store per-app data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions