hyperflask 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hyperflask might be problematic. Click here for more details.
- hyperflask/__init__.py +15 -0
- hyperflask/__main__.py +3 -0
- hyperflask/api.py +57 -0
- hyperflask/app.py +176 -0
- hyperflask/cli.py +140 -0
- hyperflask/components/__init__.py +54 -0
- hyperflask/components/alpine.py +16 -0
- hyperflask/components/core.py +60 -0
- hyperflask/components/webcomponent.py +23 -0
- hyperflask/factory.py +173 -0
- hyperflask/forms/__init__.py +51 -0
- hyperflask/forms/fields.py +17 -0
- hyperflask/forms/widgets.py +19 -0
- hyperflask/layout.html +28 -0
- hyperflask/page_helpers.py +36 -0
- hyperflask/reactivity.py +46 -0
- hyperflask/resources.py +149 -0
- hyperflask/static/alpine.js +5 -0
- hyperflask/static/app.js +5 -0
- hyperflask/static/htmx.js +5 -0
- hyperflask/static/reactive.js +5 -0
- hyperflask/static/stimulus.js +4 -0
- hyperflask/static/translations.js +55 -0
- hyperflask/ui/components/README.md +3 -0
- hyperflask/ui/components/actions/Button.html +6 -0
- hyperflask/ui/components/actions/Dropdown.html +16 -0
- hyperflask/ui/components/actions/DropdownItem.html +8 -0
- hyperflask/ui/components/actions/LinkButton.html +6 -0
- hyperflask/ui/components/actions/Modal.html +28 -0
- hyperflask/ui/components/actions/ModalAction.html +12 -0
- hyperflask/ui/components/actions/ModalTitle.html +6 -0
- hyperflask/ui/components/data-display/Accordion.html +8 -0
- hyperflask/ui/components/data-display/AccordionPanel.html +18 -0
- hyperflask/ui/components/data-input/Checkbox.html +4 -0
- hyperflask/ui/components/data-input/FileInput.html +4 -0
- hyperflask/ui/components/data-input/FormControl.html +15 -0
- hyperflask/ui/components/data-input/Input.html +4 -0
- hyperflask/ui/components/data-input/LabeledCheckbox.html +11 -0
- hyperflask/ui/components/data-input/LabeledTextInput.html +11 -0
- hyperflask/ui/components/data-input/Radiobox.html +4 -0
- hyperflask/ui/components/data-input/Range.html +4 -0
- hyperflask/ui/components/data-input/Rating.html +20 -0
- hyperflask/ui/components/data-input/Select.html +11 -0
- hyperflask/ui/components/data-input/Textarea.html +6 -0
- hyperflask/ui/components/data-input/Toggle.html +4 -0
- hyperflask/ui/components/feedback/Loading.html +4 -0
- hyperflask/ui/components/feedback/Toast.html +8 -0
- hyperflask/ui/components/layout/Footer.html +8 -0
- hyperflask/ui/components/layout/FooterLink.html +6 -0
- hyperflask/ui/components/layout/FooterNav.html +11 -0
- hyperflask/ui/components/layout/Indicator.html +11 -0
- hyperflask/ui/components/navigation/Link.html +6 -0
- hyperflask/ui/components/navigation/Navbar.html +21 -0
- hyperflask/ui/forms.html +16 -0
- hyperflask/ui/icons.html +5 -0
- hyperflask/ui/modal.html +7 -0
- hyperflask/utils/__init__.py +5 -0
- hyperflask/utils/htmx.py +24 -0
- hyperflask/utils/page_actions.py +25 -0
- hyperflask/utils/request.py +25 -0
- hyperflask/utils/tokens.py +28 -0
- hyperflask-0.1.0.dist-info/LICENSE +19 -0
- hyperflask-0.1.0.dist-info/METADATA +157 -0
- hyperflask-0.1.0.dist-info/RECORD +66 -0
- hyperflask-0.1.0.dist-info/WHEEL +4 -0
- hyperflask-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{% macro Modal(close_btn=None, open=None, position=None, responsive=True, width=None, large=None, no_blur=None) -%}
|
|
2
|
+
<dialog class="modal {{kwargs.pop('class', '')}} {% if close_btn %} {% endif %} {% if open %} modal-open{% endif %} {% if position %} modal-{{position}}{% endif %} {% if responsive %} modal-bottom sm:modal-middle{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
<div class="modal-box {% if width %} {{width}}{% endif %} {% if large %} w-11/12 max-w-full{% endif %}">
|
|
5
|
+
|
|
6
|
+
{% if not close_btn %}{% else %}<form method="dialog">
|
|
7
|
+
|
|
8
|
+
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
|
9
|
+
✕
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
</form>{% endif %}
|
|
13
|
+
|
|
14
|
+
{{ caller() }}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
{% if no_blur %}{% else %}<form method="dialog" class="modal-backdrop">
|
|
20
|
+
|
|
21
|
+
<button >
|
|
22
|
+
close
|
|
23
|
+
</button>
|
|
24
|
+
|
|
25
|
+
</form>{% endif %}
|
|
26
|
+
|
|
27
|
+
</dialog>
|
|
28
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{% macro AccordionPanel(title=None, open=None, toggle_icon=None) -%}
|
|
2
|
+
<div class="collapse bg-base-200 {% if toggle_icon %} collapse-{{toggle_icon}}{% endif %}">
|
|
3
|
+
|
|
4
|
+
<input type="radio" name="my-accordion-1" {% if open %}checked="checked"{% endif %}>
|
|
5
|
+
</input>
|
|
6
|
+
|
|
7
|
+
<div class="collapse-title text-xl font-medium">
|
|
8
|
+
{{title}}
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="collapse-content">
|
|
13
|
+
{{ caller() }}
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{% macro FileInput(bordered=None, style=None, size=None) -%}
|
|
2
|
+
<input type="file" class="file-input w-full max-w-xs {{kwargs.pop('class', '')}} {% if bordered %} file-input-bordered{% endif %} {% if style %} file-input-{{style}}{% endif %} {% if size %} file-input-{{size}}{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
</input>
|
|
4
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{% macro FormControl(label=None) -%}
|
|
2
|
+
<div class="form-control w-full {{kwargs.pop('class', '')}}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
<div class="label">
|
|
5
|
+
|
|
6
|
+
<span class="label-text">{% if label %}{{label}}{% else %}
|
|
7
|
+
{% endif %}</span>
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
{{ caller() }}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{% macro Input(bordered=True, ghost=None, style=None, size=None, type="text") -%}
|
|
2
|
+
<input class="input w-full {{kwargs.pop('class', '')}} {% if bordered %} input-bordered{% endif %} {% if ghost %} input-ghost{% endif %} {% if style %} input-{{style}}{% endif %} {% if size %} input-{{size}}{% endif %}" {% if type %}type="{{type}}"{% endif %} {{ html_attrs(kwargs) }}>
|
|
3
|
+
</input>
|
|
4
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% macro LabeledCheckbox(label, style=None, size=None) -%}
|
|
2
|
+
<label class="label cursor-pointer justify-start gap-2 {{kwargs.pop('class', '')}} {% if style %} checkbox-{{style}}{% endif %} {% if size %} checkbox-{{size}}{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
<input type="checkbox" class="checkbox">
|
|
5
|
+
</input>
|
|
6
|
+
|
|
7
|
+
<span class="label-text">{{label}}
|
|
8
|
+
</span>
|
|
9
|
+
|
|
10
|
+
</label>
|
|
11
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% macro LabeledTextInput(slot, bordered=True, ghost=None, style=None, size=None, type="text") -%}
|
|
2
|
+
<label class="input flex items-center gap-2 {% if bordered %} input-bordered{% endif %} {% if ghost %} input-ghost{% endif %} {% if style %} input-{{style}}{% endif %} {% if size %} input-{{size}}{% endif %}">
|
|
3
|
+
|
|
4
|
+
{{slot}}
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
<input {% if type %}{{type}}{% else %} text{% endif %} class="grow {{kwargs.pop('class', '')}}" {{ html_attrs(kwargs) }}>
|
|
8
|
+
</input>
|
|
9
|
+
|
|
10
|
+
</label>
|
|
11
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{% macro Rating(name=None, rating=None, mask="star", size=None) -%}
|
|
2
|
+
<div class="rating {% if size %} rating-{{size}}{% endif %}">
|
|
3
|
+
|
|
4
|
+
<input type="radio" class="mask {% if mask %} mask-{{mask}}{% endif %}" value="1" {% if name %}name="{{name}}"{% endif %} {% if rating > 0 %}checked="checked"{% endif %}>
|
|
5
|
+
</input>
|
|
6
|
+
|
|
7
|
+
<input type="radio" class="mask {% if mask %} mask-{{mask}}{% endif %}" value="2" {% if name %}name="{{name}}"{% endif %} {% if rating > 1 %}checked="checked"{% endif %}>
|
|
8
|
+
</input>
|
|
9
|
+
|
|
10
|
+
<input type="radio" class="mask {% if mask %} mask-{{mask}}{% endif %}" value="3" {% if name %}name="{{name}}"{% endif %} {% if rating > 2 %}checked="checked"{% endif %}>
|
|
11
|
+
</input>
|
|
12
|
+
|
|
13
|
+
<input type="radio" class="mask {% if mask %} mask-{{mask}}{% endif %}" value="4" {% if name %}name="{{name}}"{% endif %} {% if rating > 3 %}checked="checked"{% endif %}>
|
|
14
|
+
</input>
|
|
15
|
+
|
|
16
|
+
<input type="radio" class="mask {% if mask %} mask-{{mask}}{% endif %}" value="5" {% if name %}name="{{name}}"{% endif %} {% if rating > 4 %}checked="checked"{% endif %}>
|
|
17
|
+
</input>
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% macro Select(bordered=True, style=None, size=None, ghost=None, placeholder=None) -%}
|
|
2
|
+
<select class="select w-full max-w-xs {{kwargs.pop('class', '')}} {% if bordered %} select-bordered{% endif %} {% if style %} select-{{style}}{% endif %} {% if size %} select-{{size}}{% endif %} {% if ghost %} select-ghost{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
{% if not placeholder %}{% else %}<option disabled="" selected="">{% if placeholder %}{{placeholder}}{% else %}
|
|
5
|
+
{% endif %}</option>{% endif %}
|
|
6
|
+
|
|
7
|
+
{{ caller() }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
</select>
|
|
11
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{% macro Textarea(bordered=True, ghost=None, style=None, size=None) -%}
|
|
2
|
+
<textarea class="textarea {{kwargs.pop('class', '')}} {% if bordered %} textarea-bordered{% endif %} {% if ghost %} textarea-ghost{% endif %} {% if style %} textarea-{{style}}{% endif %} {% if size %} textarea-{{size}}{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
{{ caller() }}
|
|
4
|
+
|
|
5
|
+
</textarea>
|
|
6
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% macro FooterNav(title=None) -%}
|
|
2
|
+
<nav class="{{kwargs.pop('class', '')}}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
{% if not title %}{% else %}<h6 class="footer-title">{% if title %}{{title}}{% else %}
|
|
5
|
+
{% endif %}</h6>{% endif %}
|
|
6
|
+
|
|
7
|
+
{{ caller() }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
</nav>
|
|
11
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% macro Indicator(value=None, style="neutral", size=None, outline=None, position=None) -%}
|
|
2
|
+
<div class="indicator">
|
|
3
|
+
|
|
4
|
+
<span class="indicator-item badge {% if style %} badge-{{style}}{% endif %} {% if size %} badge-{{size}}{% endif %} {% if outline %} badge-outline{% endif %} {% if position %} indicator-{{position}}{% endif %}">{% if value %}{{value}}{% else %}
|
|
5
|
+
{% endif %}</span>
|
|
6
|
+
|
|
7
|
+
{{ caller() }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{% macro Navbar(title, title_href=None, style=None) -%}
|
|
2
|
+
<div class="navbar bg-base-100 {{kwargs.pop('class', '')}} {% if style %} bg-{{style}} text-{{style}}-content{% endif %}" {{ html_attrs(kwargs) }}>
|
|
3
|
+
|
|
4
|
+
<div class="flex-1">
|
|
5
|
+
|
|
6
|
+
<a class="btn btn-ghost text-xl" {% if title_href %}href="{{title_href}}"{% endif %}>
|
|
7
|
+
{{title}}
|
|
8
|
+
|
|
9
|
+
</a>
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="flex-none gap-2">
|
|
14
|
+
|
|
15
|
+
{{ caller() }}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
{%- endmacro %}
|
hyperflask/ui/forms.html
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
{% macro Form() -%}
|
|
3
|
+
{% set inner = caller() %}
|
|
4
|
+
<{form class__="space-y-5" method=kwargs.pop('method', 'post') **kwargs}>
|
|
5
|
+
{{form.hidden_tag()}}
|
|
6
|
+
{{inner}}
|
|
7
|
+
</{form}>
|
|
8
|
+
{%- endmacro %}
|
|
9
|
+
|
|
10
|
+
{% macro HxForm(action=None) -%}
|
|
11
|
+
{% set inner = caller() %}
|
|
12
|
+
<{Form hx_boost="false" hx_post=(action or request.url)
|
|
13
|
+
hx_on__after_request=("event.detail.successful && event.target.reset();" + kwargs.pop("hx_on__after_request", "")) **kwargs}>
|
|
14
|
+
{{inner}}
|
|
15
|
+
</{Form}>
|
|
16
|
+
{%- endmacro %}
|
hyperflask/ui/icons.html
ADDED
hyperflask/ui/modal.html
ADDED
hyperflask/utils/htmx.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from htmx_flask import request
|
|
2
|
+
from htmx_flask import make_response
|
|
3
|
+
from markupsafe import Markup
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def htmx_redirect(url):
|
|
8
|
+
return make_response(redirect=url)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def htmx_oob(html, swap="outerHTML", target=None):
|
|
12
|
+
if not request.htmx:
|
|
13
|
+
return ""
|
|
14
|
+
if callable(html):
|
|
15
|
+
html = html()
|
|
16
|
+
if target:
|
|
17
|
+
swap = f"{swap}:{target}"
|
|
18
|
+
html = str(html).strip()
|
|
19
|
+
if re.match(r'<([a-z]+)', html, re.I):
|
|
20
|
+
tag, html = html.split(" ", 1)
|
|
21
|
+
html = f"{tag} hx-swap-oob=\"{swap}\" {html}"
|
|
22
|
+
if tag[1:].lower() in ("tr", "td", "th", "thead", "tbody", "tfoot", "colgroup", "caption", "col"):
|
|
23
|
+
html = f"<template>{html}</template>"
|
|
24
|
+
return Markup(html)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from flask import request, url_for
|
|
2
|
+
from flask_file_routes import page
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def page_action(func=None, name=None):
|
|
7
|
+
def decorator(func):
|
|
8
|
+
action_name = func.__name__[3:] if not name else name
|
|
9
|
+
if request.method != "POST" or request.args.get("__action") != action_name:
|
|
10
|
+
return
|
|
11
|
+
sig = inspect.signature(func)
|
|
12
|
+
args = {}
|
|
13
|
+
for param in sig.parameters:
|
|
14
|
+
args[param.name] = request.values.get(param)
|
|
15
|
+
rv = func(**args)
|
|
16
|
+
page.respond(rv or ("", 200))
|
|
17
|
+
return decorator(func) if func else decorator
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def page_action_url(action, endpoint=None, **kwargs):
|
|
21
|
+
return url_for(endpoint or request.endpoint, __action=action, **kwargs)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def hx_page_action(action, **kwargs):
|
|
25
|
+
return 'hx-post="%s"' % page_action_url(action, **kwargs)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from flask import request
|
|
2
|
+
from urllib.parse import urlparse, urljoin
|
|
3
|
+
import functools
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_safe_redirect_url(target):
|
|
7
|
+
host_url = urlparse(request.host_url)
|
|
8
|
+
redirect_url = urlparse(urljoin(request.host_url, target))
|
|
9
|
+
return (
|
|
10
|
+
redirect_url.scheme in ("http", "https")
|
|
11
|
+
and host_url.netloc == redirect_url.netloc
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def marshal_with_depends(content_types):
|
|
16
|
+
def decorator(func):
|
|
17
|
+
@functools.wraps(func)
|
|
18
|
+
def wrapper(*args, **kwargs):
|
|
19
|
+
response = func(*args, **kwargs)
|
|
20
|
+
response.headers["Content-Type"] = content_types
|
|
21
|
+
return response
|
|
22
|
+
|
|
23
|
+
return wrapper
|
|
24
|
+
|
|
25
|
+
return decorator
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from flask import current_app, abort
|
|
2
|
+
from werkzeug.local import LocalProxy
|
|
3
|
+
from itsdangerous import URLSafeTimedSerializer, BadSignature
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_token_serializer():
|
|
7
|
+
return URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
token_serializer = LocalProxy(get_token_serializer)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_token(data, **serializer_kwargs):
|
|
14
|
+
return token_serializer.dumps(data, **serializer_kwargs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_token(token, **serializer_kwargs):
|
|
18
|
+
try:
|
|
19
|
+
return token_serializer.loads(token, **serializer_kwargs)
|
|
20
|
+
except BadSignature:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_token_or_404(token, **serializer_kwargs):
|
|
25
|
+
data = load_token(token)
|
|
26
|
+
if data is None:
|
|
27
|
+
abort(404)
|
|
28
|
+
return data
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2024 Maxime Bouroumeau-Fuseau
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hyperflask
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Full stack web framework
|
|
5
|
+
Home-page: https://github.com/hyperflask/hyperflask
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Maxime Bouroumeau-Fuseau
|
|
8
|
+
Author-email: maxime.bouroumeau@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: dramatiq-sqlite (>=0.1,<0.2)
|
|
16
|
+
Requires-Dist: flask (>=3.0,<4.0)
|
|
17
|
+
Requires-Dist: flask-apispec-hyper (>=0.12,<0.13)
|
|
18
|
+
Requires-Dist: flask-assets-pipeline (>=0.1,<0.2)
|
|
19
|
+
Requires-Dist: flask-babel-hyper (>=5.0,<6.0)
|
|
20
|
+
Requires-Dist: flask-collections (>=0.1,<0.2)
|
|
21
|
+
Requires-Dist: flask-configurator (>=0.1,<0.2)
|
|
22
|
+
Requires-Dist: flask-debugtoolbar (>=0.15,<0.16)
|
|
23
|
+
Requires-Dist: flask-dramatiq (>=0.6,<0.7)
|
|
24
|
+
Requires-Dist: flask-file-routes (>=0.1,<0.2)
|
|
25
|
+
Requires-Dist: flask-files (>=0.1,<0.2)
|
|
26
|
+
Requires-Dist: flask-geo (>=0.1,<0.2)
|
|
27
|
+
Requires-Dist: flask-mailman-templates (>=0.1,<0.2)
|
|
28
|
+
Requires-Dist: flask-mercure-sse (>=0.1,<0.2)
|
|
29
|
+
Requires-Dist: flask-sqlorm (>=0.1,<0.2)
|
|
30
|
+
Requires-Dist: flask-super-macros (>=0.1,<0.2)
|
|
31
|
+
Requires-Dist: flask-talisman (>=1.1,<2.0)
|
|
32
|
+
Requires-Dist: flask-wtf (>=1.2,<2.0)
|
|
33
|
+
Requires-Dist: frozen-flask (>=1.0,<2.0)
|
|
34
|
+
Requires-Dist: gunicorn (>=22.0,<23.0)
|
|
35
|
+
Requires-Dist: honcho (>=1.1,<2.0)
|
|
36
|
+
Requires-Dist: htmx-flask (>=0.1,<0.2)
|
|
37
|
+
Requires-Dist: jinja-layout (>=0.4,<0.5)
|
|
38
|
+
Requires-Dist: jinja-wtforms (>=0.1,<0.2)
|
|
39
|
+
Requires-Dist: jinja2-fragments (>=1.3,<2.0)
|
|
40
|
+
Requires-Dist: periodiq (>=0.12,<0.13)
|
|
41
|
+
Requires-Dist: python-slugify (>=8.0.4,<9.0.0)
|
|
42
|
+
Requires-Dist: redis (>=5.0,<6.0)
|
|
43
|
+
Project-URL: Repository, https://github.com/hyperflask/hyperflask
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
<div><img src="assets/banner.svg" width="100%"></div>
|
|
47
|
+
<hr>
|
|
48
|
+
<div align="center">
|
|
49
|
+
|
|
50
|
+
**Full stack Python web framework to build websites and web apps with as little boilerplate as possible**
|
|
51
|
+
|
|
52
|
+
**⚠️ This is a work in progress project which is not functionnal yet ⚠️**
|
|
53
|
+
|
|
54
|
+
 
|
|
55
|
+
|
|
56
|
+
[hyperflask.dev](https://hyperflask.dev) • [Get started](https://hyperflask.dev/getting-started) • [Docs](https://hyperflask.dev/guides) • [Example apps](https://github.com/hyperflask/examples)
|
|
57
|
+
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
A [Flask](https://flask.palletsprojects.com)-based (very) opiniated Python web framework where all the tech choices have been made. Hyperflask combines multiple Flask extensions and frontend libraries into a seamless experience.
|
|
61
|
+
|
|
62
|
+
**This project is part of the [Hyperflask Stack](https://hyperflask.dev).**
|
|
63
|
+
|
|
64
|
+
Features and technologies:
|
|
65
|
+
|
|
66
|
+
- Web framework built on top of [Flask](https://flask.palletsprojects.com) as a set of extensions
|
|
67
|
+
- File-based and/or app-based routing
|
|
68
|
+
- A new file format combining python code in frontmatter and html templates to define routes
|
|
69
|
+
- SQL focused ORM with [sqlorm](https://github.com/hyperflask/sqlorm), optimized for [sqlite](https://www.sqlite.org/)
|
|
70
|
+
- Modern asset pipeline using [esbuild](https://esbuild.github.io/) and [tailwindcss](https://tailwindcss.com/)
|
|
71
|
+
- Deep integration with [htmx](https://htmx.org/)
|
|
72
|
+
- Easily create reusable backend and frontend components, compatible with [Storybook](https://storybook.js.org)f
|
|
73
|
+
- Build frontend components using Web Components, [Alpine.js](https://alpinejs.dev/), [Stimulus](https://stimulus.hotwired.dev/) and more. Mix technologies at will.
|
|
74
|
+
- Component library based on [daisyUI](https://daisyui.com/) with icons from [Bootstrap Icons](https://icons.getbootstrap.com/)
|
|
75
|
+
- Seamless reactivity between frontend and backend
|
|
76
|
+
- Authentication and user management with social logins and MFA
|
|
77
|
+
- Static content collections to easily create blogs and manage static content
|
|
78
|
+
- File management with built-in image manipulation and S3 integration
|
|
79
|
+
- Template based emails with [mjml](https://mjml.io) support
|
|
80
|
+
- Background tasks using [dramatiq](https://dramatiq.io/)
|
|
81
|
+
- Push support for realtime pages using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
82
|
+
- I18n using [gettext](https://www.gnu.org/software/gettext/)
|
|
83
|
+
- Easily create REST APIs and automatically generate documentation
|
|
84
|
+
- Static, hybrid or dynamic modes for content serving
|
|
85
|
+
- Observable with [OpenTelemetry](https://opentelemetry.io/)
|
|
86
|
+
|
|
87
|
+
[Hyperflask-Start](https://github.com/hyperflask/hyperflask-start) should be used to create new projects:
|
|
88
|
+
|
|
89
|
+
- Dev environment based on [Development Containers](https://containers.dev/)
|
|
90
|
+
- Optimized for VScode with breakpoint debugging for frontend and backend
|
|
91
|
+
- [Mailpit](https://github.com/axllent/mailpit) for email testing
|
|
92
|
+
- Deployments using [Hyperflask-Deploy](https://github.com/hyperflask/hyperflask-deploy)
|
|
93
|
+
|
|
94
|
+
## Project status
|
|
95
|
+
|
|
96
|
+
**Hyperflask is being actively developed and is not yet ready to be used.**
|
|
97
|
+
|
|
98
|
+
Checkout the list of all the projects developed as part of the Hyperflask Stack and their current development status on the [Hyperflask Github organization homepage](https://github.com/hyperflask).
|
|
99
|
+
|
|
100
|
+
Status overview:
|
|
101
|
+
|
|
102
|
+
| Feature | Status |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| Core experience (start project, dev, deploy) | 🚧 |
|
|
105
|
+
| File based routing with mix code page format | ✅ |
|
|
106
|
+
| ORM | ✅ |
|
|
107
|
+
| Assets pipeline | ✅ |
|
|
108
|
+
| Component system + component library | 🚧 (component system almost done, need to create macros for daisyui) |
|
|
109
|
+
| SSE push | ✅ |
|
|
110
|
+
| Collections | 🚧 (finalizing) |
|
|
111
|
+
| Upload files | 🚧 (missing S3 improvements) |
|
|
112
|
+
| Emails | 🚧 |
|
|
113
|
+
| User management and auth | 🚧 |
|
|
114
|
+
| Static site generation | ❌ |
|
|
115
|
+
| I18n | 🚧 (finalizing) |
|
|
116
|
+
| PWA | ❌ |
|
|
117
|
+
| Runner | ✅ |
|
|
118
|
+
|
|
119
|
+
## Flask extensions
|
|
120
|
+
|
|
121
|
+
Hyperflask itself is minimal and mostly a collection of Flask extensions seamlessly integrated together.
|
|
122
|
+
|
|
123
|
+
A good part of these extensions is developed as part of the Hyperflask project. Checkout the [Hyperflask organization page](https://github.com/hyperflask) for a list of all these projects.
|
|
124
|
+
|
|
125
|
+
| Name | Description |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| [Flask-Apispec-Hyper](https://github.com/hyperflask/flask-apispec-hyper) | [Flask-Apispec](https://github.com/jmcarp/flask-apispec) fork with updates and fixes
|
|
128
|
+
| [Flask-Assets-Pipeline](https://github.com/hyperflask/flask-assets-pipeline) | Modern asset pipeline using [esbuild](https://esbuild.github.io/)
|
|
129
|
+
| [Flask-Babel-Hyper](https://github.com/hyperflask/flask-babel-hyper) | [Flask-Babel](https://github.com/python-babel/flask-babel) fork with additional utilities
|
|
130
|
+
| [Flask-Collections](https://github.com/hyperflask/flask-collections) | Manage collections of static content
|
|
131
|
+
| [Flask-Configurator](https://github.com/hyperflask/flask-configurator) | File based configuration
|
|
132
|
+
| [Flask-DebugToolbar](https://github.com/pallets-eco/flask-debugtoolbar) | Debug Toolbar
|
|
133
|
+
| [Flask-Dramatiq](https://flask-dramatiq.readthedocs.io) | Background tasks powered by [Dramatiq](https://dramatiq.io/) |
|
|
134
|
+
| [Flask-File-Routes](https://github.com/hyperflask/flask-file-routes) | File-based routing with a new file format combining python and jinja template in a single file
|
|
135
|
+
| [Flask-Files](https://github.com/hyperflask/flask-files) | [Fsspec](https://filesystem-spec.readthedocs.io/en/latest/) based files management (upload, storage and image manipulation)
|
|
136
|
+
| [Frozen-Flask](https://github.com/Frozen-Flask/Frozen) | Generate a static website from your Flask app |
|
|
137
|
+
| [Flask-Geo](https://github.com/hyperflask/flask-geo) | Geolocation using [Maxmind](https://www.maxmind.com/en/geoip-databases)
|
|
138
|
+
| [Htmx-Flask](https://github.com/sponsfreixes/htmx-flask) | HTMX integration for Flask |
|
|
139
|
+
| [Flask-Login](https://github.com/maxcountryman/flask-login) | User session management |
|
|
140
|
+
| [Flask-Mailman](https://github.com/waynerv/flask-mailman) | Send emails |
|
|
141
|
+
| [Flask-Mailman-Templates](https://github.com/hyperflask/flask-mailman-templates) | Email templates for [Flask-Mailman](https://github.com/waynerv/flask-mailman)
|
|
142
|
+
| [Flask-Mercure-SSE](https://github.com/hyperflask/flask-mercure-sse) | Push events via server-sent events using the [Mercure](https://mercure.rocks) protocol
|
|
143
|
+
| [Flask-Observability](https://github.com/hyperflask/flask-observability) | Observable Flask apps with [OpenTelemetry](https://opentelemetry.io/), logging and more
|
|
144
|
+
| [Flask-SQLORM](https://github.com/hyperflask/flask-sqlorm) | Flask integration of [sqlorm](https://github.com/hyperflask/sqlorm)
|
|
145
|
+
| [Flask-Super-Macros](https://github.com/hyperflask/flask-super-macros) | Better macro management for Jinja
|
|
146
|
+
| [Flask-Talisman](https://github.com/wntrblm/flask-talisman) | HTTP security headers for Flask
|
|
147
|
+
| [Flask-WTF](https://flask-wtf.readthedocs.io) | [WTForms](https://wtforms.readthedocs.io) integration
|
|
148
|
+
|
|
149
|
+
## Using without Hyperflask-Start
|
|
150
|
+
|
|
151
|
+
1. Create your project directory: `mkdir example-project && cd example-project`
|
|
152
|
+
2. Create and activate a virtualenv: `python -m venv .venv && source .venv/bin/activate`
|
|
153
|
+
3. `pip install hyperflask`
|
|
154
|
+
4. Create a pages directory: `mkdir pages`
|
|
155
|
+
5. Create your index page: `echo "hello world" > pages/index.html`
|
|
156
|
+
6. Start a development server using `hyperflask dev`
|
|
157
|
+
|