Project
Function
Sample CLI
gway web.app setup
References
['debug', 'info', 'resource', 'to_html', 'to_list', 'unwrap', 'version', 'web', 'web.app_url', 'web.redirect_error', 'web.static_url', 'web.work_url']
Full Code
def setup(*,
app=None,
project="web.site",
path=None,
static="static",
work="work",
home: str = "readme",
prefix: str = "view",
navbar: bool = True,
):
"""
Configure one or more Bottle-based apps. Use web server start-app to launch.
"""
global _version
_version = _version or gw.version()
# Normalize `project` into a list of project names
projects = gw.to_list(project, flat=True)
# Determine default `path` if not provided
if path is None:
first_proj = projects[0]
path = "gway" if first_proj == "web.site" else first_proj.replace(".", "/")
_is_new_app = not (app := gw.unwrap(app, Bottle) if (oapp := app) else None)
gw.debug(f"Unwrapped {app=} from {oapp=} ({_is_new_app=})")
if _is_new_app:
gw.info("No Bottle app found; creating a new Bottle app.")
app = Bottle()
# Define URL-building helpers
gw.web.static_url = lambda *args, **kwargs: build_url(static, *args, **kwargs)
gw.web.work_url = lambda *args, **kwargs: build_url(work, *args, **kwargs)
gw.web.app_url = lambda *args, **kwargs: build_url(path, *args, **kwargs)
gw.web.redirect_error = redirect_error
@app.route("/accept-cookies", method="POST")
def accept_cookies():
response.set_cookie("cookies_accepted", "yes")
redirect_url = request.forms.get("next", "/readme")
response.status = 303
if not redirect_url.startswith("/"):
redirect_url = f"/{redirect_url}"
response.set_header("Location", f"/{path}{redirect_url}")
return ""
if static:
@app.route(f"/{static}/<filename:path>")
def send_static(filename):
return static_file(filename, root=gw.resource("data", "static"))
if work:
@app.route(f"/{work}/<filename:path>")
def send_work(filename):
filename = filename.replace('-', '_')
return static_file(filename, root=gw.resource("work", "shared"))
# TODO: When passing a param to a view, such as from a form that was left empty by the user
# avoid passing None, instead pass "" (empty string) as it may differentiate between a
# form that has not been submitted and one submitted with empty fields.
@app.route(f"/{path}/<view:path>", method=["GET", "POST"])
def view_dispatch(view):
nonlocal navbar
segments = [s for s in view.strip("/").split("/") if s]
if not segments:
segments = [home]
view_name = segments[0].replace("-", "_")
args = segments[1:]
kwargs = dict(request.query)
if request.method == "POST":
try:
if request.json:
kwargs.update(request.json)
elif request.forms:
kwargs.update(request.forms.decode())
except Exception as e:
return redirect_error(e, note="Error loading JSON payload", broken_view_name=view_name)
sources = []
for proj_name in projects:
try:
sources.append(gw[proj_name])
except Exception:
continue
for source in sources:
view_func = getattr(source, f"{prefix}_{view_name}", None)
if callable(view_func):
break
else:
return redirect_error(
note=f"View '{prefix}_{view_name}' not found in any project: {projects}",
broken_view_name=view_name,
default=f"/{path}/{home}" if path and home else "/gway/readme"
)
try:
gw.debug(f"Dispatch to {view_func.__name__} (args={args}, kwargs={kwargs})")
content = view_func(*args, **kwargs)
if content and not isinstance(content, str):
content = gw.to_html(content)
visited = update_visited(view_name)
except HTTPResponse as resp:
return resp
except Exception as e:
return redirect_error(e, note="Error during view execution", broken_view_name=view_name)
full_url = request.fullpath
if request.query_string:
full_url += "?" + request.query_string
if navbar is True:
navbar = render_navbar(visited, path, current_url=full_url)
if not cookies_enabled():
consent_box = f"""
<div class="consent-box">
<form action="/accept-cookies" method="post">
<input type="hidden" name="next" value="/{view}" />
This site uses cookies to improve your experience.
<button type="submit">Accept</button>
</form>
</div>
"""
content = consent_box + content
style_param = request.query.get("css") or request.query.get("style")
if style_param:
if not style_param.endswith(".css"):
style_param += ".css"
response.set_cookie("css", style_param, path="/")
css_files = ["default.css", style_param]
else:
css_cookie = request.get_cookie("css", "")
css_files = ["default.css"] + [c.strip() for c in css_cookie.split(",") if c.strip()]
return render_template(
title="GWAY - " + view_name.replace("_", " ").title(),
navbar=navbar,
content=content,
static=static,
css_files=css_files
)
@app.route("/", method=["GET", "POST"])
def index():
response.status = 302
response.set_header("Location", f"/{path}/readme")
return ""
@app.error(404)
def handle_404(error):
fallback = "/gway/readme"
try:
return redirect_error(
error,
note=f"404 Not Found: {request.url}",
default=f"/{path}/{home}" if path and home else fallback
)
except Exception as e:
return redirect_error(e, note="Failed during 404 fallback", default=fallback)
if _is_new_app:
app = security_middleware(app)
return app if not oapp else (oapp, app)
return oapp