hyperflask 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- {hyperflask-0.3.0 → hyperflask-0.4.0}/PKG-INFO +7 -6
- {hyperflask-0.3.0 → hyperflask-0.4.0}/pyproject.toml +7 -8
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/__init__.py +2 -0
- hyperflask-0.4.0/src/hyperflask/actors.py +82 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/app.py +39 -19
- hyperflask-0.4.0/src/hyperflask/cli/__init__.py +53 -0
- hyperflask-0.4.0/src/hyperflask/cli/reloader.py +24 -0
- hyperflask-0.3.0/src/hyperflask/cli.py → hyperflask-0.4.0/src/hyperflask/cli/runner.py +17 -49
- hyperflask-0.4.0/src/hyperflask/cli/worker.py +90 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/components/__init__.py +4 -3
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/components/alpine.py +1 -1
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/components/core.py +48 -30
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/components/jsx.py +1 -1
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/components/webcomponent.py +1 -1
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/factory.py +4 -3
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/layouts/web.html +1 -8
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/model.py +25 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/htmx-exts.js +8 -5
- hyperflask-0.4.0/src/hyperflask/utils/metadata.py +182 -0
- hyperflask-0.3.0/src/hyperflask/ui/metadata.html +0 -4
- {hyperflask-0.3.0 → hyperflask-0.4.0}/README.md +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/__main__.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/forms/__init__.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/forms/fields.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/forms/widgets.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/layouts/email.mjml +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/page_helpers.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/security.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/alpine-csp.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/alpine.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/app.css +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/app.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/react.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/stimulus.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/static/translations.js +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Accordion.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/AccordionPanel.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Alert.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Avatar.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/AvatarGroup.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Badge.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Breadcrumbs.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/BreadcrumbsItem.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Button.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Card.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/CardActions.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/CardTitle.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/ChatBubble.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Checkbox.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/CollapsibleSubMenu.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Countdown.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Divider.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Drawer.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/DrawerContent.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/DrawerSide.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Dropdown.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/DropdownItem.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Fieldset.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/FieldsetLegend.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/FileInput.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Filter.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/FilterItem.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Footer.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/FooterLink.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/FooterNav.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Indicator.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Input.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/InputLabel.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Join.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Kbd.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Label.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/LabeledCheckbox.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/LabeledToggle.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Link.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/List.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/ListItem.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Loading.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Menu.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/MenuItem.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/MenuTitle.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Modal.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/ModalAction.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/ModalTitle.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Navbar.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Pagination.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/PaginationNext.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/PaginationPage.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/PaginationPrev.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Progress.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/README.md +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Radio.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Range.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Rating.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Select.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Stack.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Stat.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Stats.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Step.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Steps.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/SubMenu.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Swap.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Tab.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Table.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Tabs.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Textarea.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Toast.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Toggle.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/daisyui/Tooltip.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/flash.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/forms.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/icons.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/layout.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/modal.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/skeleton.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/ui/sse.html +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/__init__.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/html.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/htmx.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/markdown.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/page_actions.py +0 -0
- {hyperflask-0.3.0 → hyperflask-0.4.0}/src/hyperflask/utils/tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperflask
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Full stack web framework
|
|
5
5
|
Author: Maxime Bouroumeau-Fuseau
|
|
6
6
|
Author-email: Maxime Bouroumeau-Fuseau <maxime.bouroumeau@gmail.com>
|
|
@@ -11,26 +11,27 @@ Requires-Dist: flask-babel-hyper~=5.1
|
|
|
11
11
|
Requires-Dist: flask-collections~=0.2
|
|
12
12
|
Requires-Dist: flask-configurator~=0.1
|
|
13
13
|
Requires-Dist: flask-debugtoolbar~=0.16
|
|
14
|
-
Requires-Dist: flask-dramatiq~=0.6
|
|
15
14
|
Requires-Dist: flask-file-routes~=0.3
|
|
16
15
|
Requires-Dist: flask-files~=0.2
|
|
17
16
|
Requires-Dist: flask-geo~=0.1
|
|
18
17
|
Requires-Dist: flask-mailman-templates~=0.1
|
|
19
18
|
Requires-Dist: flask-mercure-sse~=0.3
|
|
20
19
|
Requires-Dist: flask-sqlorm~=0.3
|
|
21
|
-
Requires-Dist: flask-super-macros~=0.
|
|
22
|
-
Requires-Dist: flask-suspense~=0.
|
|
20
|
+
Requires-Dist: flask-super-macros~=0.2
|
|
21
|
+
Requires-Dist: flask-suspense~=0.3
|
|
23
22
|
Requires-Dist: flask-wtf~=1.2
|
|
24
23
|
Requires-Dist: frozen-flask~=1.0
|
|
24
|
+
Requires-Dist: dramatiq>=1.18.0
|
|
25
|
+
Requires-Dist: dramatiq-sqlite~=0.1
|
|
25
26
|
Requires-Dist: gunicorn~=23.0
|
|
26
27
|
Requires-Dist: honcho~=2.0
|
|
27
28
|
Requires-Dist: html-sanitizer~=2.6
|
|
28
29
|
Requires-Dist: htmx-flask~=0.1
|
|
29
|
-
Requires-Dist: jinja-layout~=0.
|
|
30
|
+
Requires-Dist: jinja-layout~=0.5
|
|
30
31
|
Requires-Dist: jinja-wtforms~=0.2
|
|
31
32
|
Requires-Dist: periodiq~=0.13
|
|
32
33
|
Requires-Dist: python-slugify~=8.0.4
|
|
33
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: watchdog>=6.0.0
|
|
34
35
|
Requires-Python: >=3.10
|
|
35
36
|
Project-URL: Repository, https://github.com/hyperflask/hyperflask
|
|
36
37
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hyperflask"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Full stack web framework"
|
|
5
5
|
authors = [
|
|
6
6
|
{"name" = "Maxime Bouroumeau-Fuseau", email = "maxime.bouroumeau@gmail.com"}
|
|
@@ -15,7 +15,6 @@ dependencies = [
|
|
|
15
15
|
"flask-collections ~=0.2",
|
|
16
16
|
"flask-configurator ~=0.1",
|
|
17
17
|
"flask-debugtoolbar ~=0.16",
|
|
18
|
-
"flask-dramatiq ~=0.6",
|
|
19
18
|
"flask-file-routes ~=0.3",
|
|
20
19
|
"flask-files ~=0.2",
|
|
21
20
|
"flask-geo ~=0.1",
|
|
@@ -23,24 +22,24 @@ dependencies = [
|
|
|
23
22
|
"flask-mercure-sse ~=0.3",
|
|
24
23
|
#"flask-observability ~=0.1"
|
|
25
24
|
"flask-sqlorm ~=0.3",
|
|
26
|
-
"flask-super-macros ~=0.
|
|
27
|
-
"flask-suspense ~=0.
|
|
25
|
+
"flask-super-macros ~=0.2",
|
|
26
|
+
"flask-suspense ~=0.3",
|
|
28
27
|
"flask-wtf ~=1.2",
|
|
29
28
|
"frozen-flask ~=1.0",
|
|
30
|
-
|
|
29
|
+
"dramatiq>=1.18.0",
|
|
30
|
+
"dramatiq-sqlite ~=0.1",
|
|
31
31
|
"gunicorn ~=23.0",
|
|
32
32
|
"honcho ~=2.0",
|
|
33
33
|
"html-sanitizer ~=2.6",
|
|
34
34
|
"htmx-flask ~=0.1",
|
|
35
|
-
"jinja-layout ~=0.
|
|
35
|
+
"jinja-layout ~=0.5",
|
|
36
36
|
"jinja-wtforms ~=0.2",
|
|
37
37
|
"periodiq ~=0.13",
|
|
38
38
|
"python-slugify ~=8.0.4",
|
|
39
|
-
"
|
|
39
|
+
"watchdog>=6.0.0",
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[tool.uv.sources]
|
|
43
|
-
flask-dramatiq = { git = "https://gitlab.com/bersace/flask-dramatiq" }
|
|
44
43
|
periodiq = { git = "https://gitlab.com/bersace/periodiq" }
|
|
45
44
|
|
|
46
45
|
[project.urls]
|
|
@@ -10,8 +10,10 @@ from flask_files import save_file, validate_file
|
|
|
10
10
|
from flask_mailman_templates import send_mail
|
|
11
11
|
from flask_mercure_sse import publish_signal, mercure_publish
|
|
12
12
|
from flask_suspense import render_template, stream_template, defer
|
|
13
|
+
from flask_assets_pipeline import asset_url, static_url
|
|
13
14
|
|
|
14
15
|
# useful exports from 3rd party libs
|
|
15
16
|
from htmx_flask import request, make_response
|
|
16
17
|
from slugify import slugify
|
|
17
18
|
from blinker import signal
|
|
19
|
+
from periodiq import cron
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from threading import local
|
|
2
|
+
from dramatiq import Middleware, actor
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DRAMATIQ_BROKERS = {
|
|
7
|
+
"amqp": "dramatiq.brokers.rabbitmq:RabbitmqBroker",
|
|
8
|
+
"redis": "dramatiq.brokers.redis:RedisBroker",
|
|
9
|
+
"sqlite": "dramatiq_sqlite:SQLiteBroker",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def discover_broker(broker_cls=None, broker_url=None):
|
|
14
|
+
if not broker_url and broker_cls and "://" in broker_cls:
|
|
15
|
+
broker_url = broker_cls
|
|
16
|
+
broker_cls = None
|
|
17
|
+
if not broker_cls and broker_url:
|
|
18
|
+
if "://" not in broker_url:
|
|
19
|
+
broker_name = "sqlite"
|
|
20
|
+
else:
|
|
21
|
+
broker_name, broker_url = broker_url.split("://", 1)
|
|
22
|
+
if broker_name not in DRAMATIQ_BROKERS:
|
|
23
|
+
raise ValueError(f"Unknown dramatiq broker '{broker_name}'")
|
|
24
|
+
broker_cls = DRAMATIQ_BROKERS[broker_name]
|
|
25
|
+
if isinstance(broker_cls, str):
|
|
26
|
+
module_name, cls_name = broker_cls.split(":")
|
|
27
|
+
module = importlib.import_module(module_name)
|
|
28
|
+
broker_cls = getattr(module, cls_name)
|
|
29
|
+
return broker_cls, broker_url
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def make_actor_decorator(app):
|
|
33
|
+
def actor_decorator(**kw):
|
|
34
|
+
def decorator(fn):
|
|
35
|
+
if getattr(app, 'dramatiq_broker', None):
|
|
36
|
+
kw['broker'] = app.dramatiq_broker
|
|
37
|
+
return actor(**kw)(fn)
|
|
38
|
+
return UndefinedBrokerActor(fn)
|
|
39
|
+
return decorator
|
|
40
|
+
return actor_decorator
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AppContextMiddleware(Middleware):
|
|
44
|
+
# Setup Flask app for actor. Borrowed from
|
|
45
|
+
# https://github.com/Bogdanp/flask_dramatiq_example.
|
|
46
|
+
|
|
47
|
+
state = local()
|
|
48
|
+
|
|
49
|
+
def __init__(self, app):
|
|
50
|
+
self.app = app
|
|
51
|
+
|
|
52
|
+
def before_process_message(self, broker, message):
|
|
53
|
+
context = self.app.app_context()
|
|
54
|
+
context.push()
|
|
55
|
+
|
|
56
|
+
self.state.context = context
|
|
57
|
+
|
|
58
|
+
def after_process_message(
|
|
59
|
+
self, broker, message, *, result=None, exception=None):
|
|
60
|
+
try:
|
|
61
|
+
context = self.state.context
|
|
62
|
+
context.pop(exception)
|
|
63
|
+
del self.state.context
|
|
64
|
+
except AttributeError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
after_skip_message = after_process_message
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class UndefinedBrokerActor:
|
|
71
|
+
def __init__(self, fn):
|
|
72
|
+
self.fn = fn
|
|
73
|
+
self.__name__ = fn.__name__
|
|
74
|
+
|
|
75
|
+
def __call__(self, *args, **kwargs):
|
|
76
|
+
return self.fn(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
def send(self, *args, **kwargs):
|
|
79
|
+
raise RuntimeError("No broker defined for Dramatiq actors.")
|
|
80
|
+
|
|
81
|
+
def send_with_options(self, *args, **kwargs):
|
|
82
|
+
raise RuntimeError("No broker defined for Dramatiq actors.")
|
|
@@ -5,7 +5,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
5
5
|
# hyperflask extensions
|
|
6
6
|
from jinja_layout import LayoutExtension
|
|
7
7
|
from jinja_wtforms import WtformExtension
|
|
8
|
-
from flask_assets_pipeline import AssetsPipeline
|
|
8
|
+
from flask_assets_pipeline import AssetsPipeline, asset_url, static_url
|
|
9
9
|
from flask_super_macros import SuperMacros
|
|
10
10
|
from flask_collections import Collections
|
|
11
11
|
from flask_file_routes import FileRoutes, ModuleView, page
|
|
@@ -16,14 +16,13 @@ from flask_files import Files
|
|
|
16
16
|
from flask_sqlorm import FlaskSQLORM
|
|
17
17
|
from flask_mercure_sse import MercureSSE
|
|
18
18
|
from flask_mailman_templates import MailTemplates
|
|
19
|
-
from flask_suspense import Suspense, render_template, suspense_before_render_template,
|
|
19
|
+
from flask_suspense import Suspense, render_template, suspense_before_render_template, suspense_before_render_macros
|
|
20
20
|
#from flask_observability import Observability
|
|
21
21
|
# 3rd party extensions
|
|
22
22
|
from flask_wtf.csrf import CSRFProtect
|
|
23
23
|
from flask_frozen import Freezer
|
|
24
24
|
from htmx_flask import Htmx
|
|
25
25
|
from flask_mailman import Mail
|
|
26
|
-
from flask_dramatiq import Dramatiq
|
|
27
26
|
from flask_debugtoolbar import DebugToolbarExtension
|
|
28
27
|
# from hyperflask
|
|
29
28
|
from .forms import Form
|
|
@@ -31,8 +30,10 @@ from .utils.page_actions import page_action_url
|
|
|
31
30
|
from .utils.htmx import htmx_oob, respond_with_flash_messages
|
|
32
31
|
from .utils.markdown import jinja_markdown, MarkdownExtension
|
|
33
32
|
from .utils.html import sanitize_html, nl2br
|
|
33
|
+
from .utils.metadata import metadata_tags
|
|
34
34
|
from .components import register_components
|
|
35
|
-
from .model import Model, File as SQLFileType
|
|
35
|
+
from .model import Model, File as SQLFileType, UndefinedDatabase
|
|
36
|
+
from .actors import AppContextMiddleware, discover_broker, make_actor_decorator
|
|
36
37
|
from .security import respond_with_security_headers, csp_nonce
|
|
37
38
|
from . import page_helpers
|
|
38
39
|
# others
|
|
@@ -40,6 +41,7 @@ from jinja_super_macros.registry import FileLoader
|
|
|
40
41
|
from jinja_wtforms.extractor import map_jinja_call_node_to_func
|
|
41
42
|
from sqlorm.sql_template import SQLTemplate
|
|
42
43
|
from periodiq import PeriodiqMiddleware
|
|
44
|
+
import dramatiq
|
|
43
45
|
import os
|
|
44
46
|
|
|
45
47
|
|
|
@@ -55,7 +57,7 @@ class Hyperflask(Flask):
|
|
|
55
57
|
|
|
56
58
|
def __init__(self, *args, static_mode="hybrid", instrument=False, layouts_folder="layouts",
|
|
57
59
|
emails_folder="emails", forms_folder="forms", assets_folder="assets", pages_folder="pages", migrations_folder="migrations",
|
|
58
|
-
config=None, config_filename="config.yml", database_uri=
|
|
60
|
+
config=None, config_filename="config.yml", database_uri=None, proxy_fix=True, **kwargs):
|
|
59
61
|
|
|
60
62
|
super().__init__(*args, **kwargs)
|
|
61
63
|
if proxy_fix:
|
|
@@ -66,10 +68,10 @@ class Hyperflask(Flask):
|
|
|
66
68
|
"FREEZER_STATIC_IGNORE": [],
|
|
67
69
|
"WTF_CSRF_CHECK_DEFAULT": False,
|
|
68
70
|
"OTEL_INSTRUMENT": instrument,
|
|
69
|
-
"DRAMATIQ_BROKER": "dramatiq.brokers.redis:RedisBroker",
|
|
70
71
|
"DEBUG_TB_INTERCEPT_REDIRECTS": False,
|
|
71
72
|
"SESSION_COOKIE_HTTPONLY": True,
|
|
72
73
|
"SESSION_COOKIE_SAMESITE": "Lax",
|
|
74
|
+
"SQLORM_URI": database_uri,
|
|
73
75
|
# Hyperflask specific
|
|
74
76
|
"LAYOUT": "layouts/default.html",
|
|
75
77
|
"ALPINE": False,
|
|
@@ -117,7 +119,7 @@ class Hyperflask(Flask):
|
|
|
117
119
|
self.jinja_env.add_extension(WtformExtension)
|
|
118
120
|
self.jinja_env.add_extension(MarkdownExtension)
|
|
119
121
|
self.jinja_env.filters.update(markdown=jinja_markdown, sanitize=sanitize_html, nl2br=nl2br)
|
|
120
|
-
self.jinja_env.globals.update(page_action_url=page_action_url, app=self, csp_nonce=csp_nonce)
|
|
122
|
+
self.jinja_env.globals.update(page_action_url=page_action_url, app=self, csp_nonce=csp_nonce, metadata_tags=metadata_tags)
|
|
121
123
|
self.jinja_env.form_base_cls = Form
|
|
122
124
|
self.jinja_env.default_layout = self.config["LAYOUT"]
|
|
123
125
|
self.forms = self.jinja_env.forms
|
|
@@ -133,14 +135,11 @@ class Hyperflask(Flask):
|
|
|
133
135
|
])
|
|
134
136
|
|
|
135
137
|
DebugToolbarExtension(self)
|
|
136
|
-
self.freezer = Freezer(self)
|
|
138
|
+
self.freezer = Freezer(self, with_no_argument_rules=False)
|
|
139
|
+
self.freezer.register_generator(lambda: freezer_url_generator(self))
|
|
137
140
|
self.csrf = CSRFProtect(self)
|
|
138
|
-
#self.talisman = Talisman(self)
|
|
139
141
|
Htmx(self)
|
|
140
142
|
Mail(self)
|
|
141
|
-
self.dramatiq = Dramatiq(self)
|
|
142
|
-
self.dramatiq.broker.add_middleware(PeriodiqMiddleware())
|
|
143
|
-
self.actor = self.dramatiq.actor
|
|
144
143
|
|
|
145
144
|
SuperMacros(self)
|
|
146
145
|
self.components = self.macros # alias
|
|
@@ -205,10 +204,20 @@ class Hyperflask(Flask):
|
|
|
205
204
|
|
|
206
205
|
self.collections.register_freezer_generator(self.freezer)
|
|
207
206
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
if self.config.get('SQLORM_URI'):
|
|
208
|
+
SQLTemplate.eval_globals.update(app=self)
|
|
209
|
+
self.db = FlaskSQLORM(self, migrations_folder=migrations_folder)
|
|
210
|
+
self.db.Model = Model
|
|
211
|
+
self.db.File = SQLFileType
|
|
212
|
+
else:
|
|
213
|
+
self.db = UndefinedDatabase()
|
|
214
|
+
|
|
215
|
+
broker_cls, broker_url = discover_broker(self.config.get('DRAMATIQ_BROKER'), self.config.get('DRAMATIQ_BROKER_URL'))
|
|
216
|
+
if broker_cls:
|
|
217
|
+
self.dramatiq_broker = broker_cls(url=broker_url)
|
|
218
|
+
self.dramatiq_broker.add_middleware(AppContextMiddleware(self))
|
|
219
|
+
self.dramatiq_broker.add_middleware(PeriodiqMiddleware())
|
|
220
|
+
self.actor = make_actor_decorator(self)
|
|
212
221
|
|
|
213
222
|
Suspense(self, nonce_getter="csp_nonce()")
|
|
214
223
|
|
|
@@ -216,9 +225,10 @@ class Hyperflask(Flask):
|
|
|
216
225
|
def on_suspense_before_render_template(sender, **kwargs):
|
|
217
226
|
csp_nonce() # we will need a nonce but the first call will be after the template started rendering
|
|
218
227
|
|
|
219
|
-
@
|
|
220
|
-
def
|
|
221
|
-
return
|
|
228
|
+
@suspense_before_render_macros.connect_via(self, weak=False)
|
|
229
|
+
def on_suspense_before_render_macros(sender, **kwargs):
|
|
230
|
+
return ""
|
|
231
|
+
return f'<script nonce="{csp_nonce()}">htmx.bootstrap()</script>' # waiting on PR 3441
|
|
222
232
|
|
|
223
233
|
@self.errorhandler(404)
|
|
224
234
|
def not_found(error):
|
|
@@ -239,6 +249,8 @@ class Hyperflask(Flask):
|
|
|
239
249
|
|
|
240
250
|
|
|
241
251
|
class HyperModuleView(ModuleView):
|
|
252
|
+
module_globals = dict(ModuleView.module_globals, asset_url=asset_url, static_url=static_url)
|
|
253
|
+
|
|
242
254
|
def _render_template(self):
|
|
243
255
|
return render_template(page.template)
|
|
244
256
|
|
|
@@ -251,3 +263,11 @@ def dynamic(func=None, static_template=None):
|
|
|
251
263
|
def decorator(func):
|
|
252
264
|
pass
|
|
253
265
|
return decorator(func) if func else decorator
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def freezer_url_generator(app):
|
|
269
|
+
"""URL generator for URL rules that take no arguments."""
|
|
270
|
+
for rule in app.url_map.iter_rules():
|
|
271
|
+
if not rule.arguments and 'GET' in rule.methods and not rule.endpoint.startswith('mercure'):
|
|
272
|
+
app.logger.info(rule.endpoint)
|
|
273
|
+
yield rule.endpoint, {}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from flask import current_app
|
|
2
|
+
from flask.cli import FlaskGroup, shell_command, routes_command, ScriptInfo, NoAppException
|
|
3
|
+
from flask_assets_pipeline import cli as assets_cli
|
|
4
|
+
import click
|
|
5
|
+
from ..factory import create_app
|
|
6
|
+
from ..security import generate_csp_policy
|
|
7
|
+
from .runner import serve_command, run_command, dev_command
|
|
8
|
+
from .worker import worker_command, scheduler_command
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _create_app():
|
|
12
|
+
info = ScriptInfo()
|
|
13
|
+
try:
|
|
14
|
+
return info.load_app()
|
|
15
|
+
except NoAppException:
|
|
16
|
+
return create_app()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
cli = FlaskGroup(name='kantree', help='Kantree', add_default_commands=False, create_app=_create_app)
|
|
20
|
+
cli.add_command(shell_command)
|
|
21
|
+
cli.add_command(routes_command)
|
|
22
|
+
cli.add_command(serve_command)
|
|
23
|
+
cli.add_command(run_command)
|
|
24
|
+
cli.add_command(dev_command)
|
|
25
|
+
cli.add_command(worker_command)
|
|
26
|
+
cli.add_command(scheduler_command)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cli.command("init")
|
|
30
|
+
def init_command():
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@cli.command("freeze")
|
|
35
|
+
def freeze_command():
|
|
36
|
+
current_app.freezer.freeze()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@cli.command("build")
|
|
40
|
+
@click.pass_context
|
|
41
|
+
def build_command(ctx):
|
|
42
|
+
ctx.invoke(assets_cli.build)
|
|
43
|
+
ctx.invoke(freeze_command)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@cli.command("csp-header")
|
|
47
|
+
def csp_header_command():
|
|
48
|
+
click.echo(generate_csp_policy())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
cli.main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from werkzeug._reloader import WatchdogReloaderLoop, reloader_loops
|
|
2
|
+
import os.path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# watchdog is a dependency of hyperflask so it would always be selected as the reloader
|
|
6
|
+
class WatchdogWithExtraFilesPatternsReloaderLoop(WatchdogReloaderLoop):
|
|
7
|
+
def __init__(self, *args, extra_files=None, **kwargs):
|
|
8
|
+
# VERY HACKY SOLUTION !
|
|
9
|
+
# in werkzeug reloader, it is not possible to add patterns as extra_files
|
|
10
|
+
# to circumvent this and avoid copy/pasting big chunk of code, we patch os.path.abspath
|
|
11
|
+
# which is used to process the list of extra_files in the __init__
|
|
12
|
+
extra_files = list(extra_files) if extra_files else []
|
|
13
|
+
extra_files.append("*.jpy")
|
|
14
|
+
abspath = os.path.abspath
|
|
15
|
+
def patched_abspath(path):
|
|
16
|
+
if path.startswith("*."):
|
|
17
|
+
return path
|
|
18
|
+
return abspath(path)
|
|
19
|
+
os.path.abspath = patched_abspath
|
|
20
|
+
super().__init__(*args, extra_files=extra_files, **kwargs)
|
|
21
|
+
os.path.abspath = abspath
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
reloader_loops["auto"] = WatchdogWithExtraFilesPatternsReloaderLoop
|
|
@@ -1,41 +1,12 @@
|
|
|
1
|
-
from flask import
|
|
2
|
-
from flask.cli import FlaskGroup, shell_command, routes_command, _debug_option, pass_script_info, run_command as flask_run_command, ScriptInfo, NoAppException
|
|
3
|
-
from flask_assets_pipeline import cli as assets_cli
|
|
1
|
+
from flask.cli import _debug_option, pass_script_info, run_command as flask_run_command
|
|
4
2
|
import click
|
|
5
3
|
import os
|
|
6
4
|
import sys
|
|
7
5
|
import multiprocessing
|
|
8
6
|
import gunicorn.app.base
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _create_app():
|
|
13
|
-
info = ScriptInfo()
|
|
14
|
-
try:
|
|
15
|
-
return info.load_app()
|
|
16
|
-
except NoAppException:
|
|
17
|
-
return create_app()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
cli = FlaskGroup(name='kantree', help='Kantree', add_default_commands=False, create_app=_create_app)
|
|
21
|
-
cli.add_command(shell_command)
|
|
22
|
-
cli.add_command(routes_command)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@click.command("freeze")
|
|
26
|
-
def freeze_command():
|
|
27
|
-
current_app.freezer.freeze()
|
|
28
|
-
|
|
29
|
-
cli.add_command(freeze_command)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@click.command("build")
|
|
33
|
-
@click.pass_context
|
|
34
|
-
def build_command(ctx):
|
|
35
|
-
ctx.invoke(assets_cli.build)
|
|
36
|
-
ctx.invoke(freeze_command)
|
|
37
|
-
|
|
38
|
-
cli.add_command(build_command)
|
|
7
|
+
from honcho.manager import Manager as BaseProcessManager
|
|
8
|
+
from ..factory import load_config
|
|
9
|
+
from . import reloader # import hacky patch of werkzeug reloader
|
|
39
10
|
|
|
40
11
|
|
|
41
12
|
@click.command("serve")
|
|
@@ -62,7 +33,6 @@ def serve_command(ctx, info, host, port, gunicorn_opt, **run_kwargs):
|
|
|
62
33
|
|
|
63
34
|
|
|
64
35
|
serve_command.params = flask_run_command.params + serve_command.params
|
|
65
|
-
cli.add_command(serve_command)
|
|
66
36
|
|
|
67
37
|
|
|
68
38
|
@click.command("run")
|
|
@@ -74,7 +44,6 @@ cli.add_command(serve_command)
|
|
|
74
44
|
@click.option("--dev", is_flag=True)
|
|
75
45
|
def run_command(processes, host, port, concurrency, extend_procfile, dev):
|
|
76
46
|
from honcho.command import _procfile, _parse_concurrency
|
|
77
|
-
from honcho.manager import Manager
|
|
78
47
|
from honcho.printer import Printer
|
|
79
48
|
from honcho import environ
|
|
80
49
|
|
|
@@ -92,24 +61,27 @@ def run_command(processes, host, port, concurrency, extend_procfile, dev):
|
|
|
92
61
|
_processes = {}
|
|
93
62
|
_processes.update({
|
|
94
63
|
"web": [sys.argv[0], "serve", "--host", host, "--port", "$PORT"],
|
|
95
|
-
|
|
96
|
-
|
|
64
|
+
"worker": [sys.argv[0], "worker"],
|
|
65
|
+
"scheduler": [sys.argv[0], "scheduler"]
|
|
97
66
|
})
|
|
98
67
|
if dev:
|
|
99
68
|
_processes["assets"] = [sys.argv[0], "assets", "dev"]
|
|
69
|
+
_processes["worker"].extend(["-p", "1", "-t", "1"])
|
|
100
70
|
else:
|
|
101
71
|
_processes["mercurehub"] = [sys.executable, "-m", "flask_mercure_sse.server"]
|
|
102
72
|
|
|
103
73
|
processes = {name: " ".join(_processes[name]) if isinstance(_processes[name], list) else _processes[name]
|
|
104
74
|
for name in _processes if name in _processes}
|
|
105
75
|
|
|
106
|
-
manager =
|
|
76
|
+
manager = ProcessManager(Printer(sys.stdout, colour=True))
|
|
107
77
|
|
|
108
78
|
for p in environ.expand_processes(processes,
|
|
109
79
|
concurrency=_parse_concurrency(concurrency),
|
|
110
80
|
port=port):
|
|
111
81
|
e = os.environ.copy()
|
|
112
82
|
e.update(p.env)
|
|
83
|
+
if p.name != "scheduler":
|
|
84
|
+
e["FLASK_SKIP_DB_INIT"] = "0"
|
|
113
85
|
manager.add_process(p.name, p.cmd, quiet=p.quiet, env=e)
|
|
114
86
|
|
|
115
87
|
manager.loop()
|
|
@@ -117,7 +89,6 @@ def run_command(processes, host, port, concurrency, extend_procfile, dev):
|
|
|
117
89
|
|
|
118
90
|
|
|
119
91
|
run_command.params.insert(0, _debug_option)
|
|
120
|
-
cli.add_command(run_command)
|
|
121
92
|
|
|
122
93
|
|
|
123
94
|
@click.command("dev")
|
|
@@ -127,18 +98,15 @@ def dev_command(ctx, **kwargs):
|
|
|
127
98
|
|
|
128
99
|
|
|
129
100
|
dev_command.params = list(run_command.params)
|
|
130
|
-
cli.add_command(dev_command)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@click.command("init")
|
|
134
|
-
def init_command():
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
cli.add_command(init_command)
|
|
138
101
|
|
|
139
102
|
|
|
140
|
-
|
|
141
|
-
|
|
103
|
+
class ProcessManager(BaseProcessManager):
|
|
104
|
+
# This override prevents the manager to stop if the worker and scheduler exit because dramatiq is unused
|
|
105
|
+
def _any_stopped(self):
|
|
106
|
+
def has_exited_because_dramatiq_unavailable(name, p):
|
|
107
|
+
return name.split('.')[0] in ('scheduler', 'worker') and p.get('returncode') == 10
|
|
108
|
+
return any(not has_exited_because_dramatiq_unavailable(name, p) and p.get('returncode') is not None
|
|
109
|
+
for name, p in self._processes.items())
|
|
142
110
|
|
|
143
111
|
|
|
144
112
|
class GunicornServer(gunicorn.app.base.BaseApplication):
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
from flask import current_app
|
|
4
|
+
from flask.cli import with_appcontext
|
|
5
|
+
import periodiq
|
|
6
|
+
from dramatiq import set_broker
|
|
7
|
+
from dramatiq.cli import (
|
|
8
|
+
CPUS,
|
|
9
|
+
main as dramatiq_worker,
|
|
10
|
+
make_argument_parser as dramatiq_argument_parser,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command("worker")
|
|
15
|
+
@click.option('-v', '--verbose', is_flag=True)
|
|
16
|
+
@click.option('-p', '--processes', default=CPUS,
|
|
17
|
+
metavar='PROCESSES', show_default=True,
|
|
18
|
+
help="the number of worker processes to run")
|
|
19
|
+
@click.option('-t', '--threads', default=8,
|
|
20
|
+
metavar='THREADS', show_default=True,
|
|
21
|
+
help="the number of worker threads per process")
|
|
22
|
+
@click.option('-Q', '--queues', type=str, default=None,
|
|
23
|
+
metavar='QUEUES', show_default=True,
|
|
24
|
+
help="listen to a subset of queues, comma separated")
|
|
25
|
+
@with_appcontext
|
|
26
|
+
def worker_command(verbose, processes, threads, queues):
|
|
27
|
+
"""Run dramatiq workers.
|
|
28
|
+
"""
|
|
29
|
+
check_dramatiq_availability()
|
|
30
|
+
|
|
31
|
+
parser = dramatiq_argument_parser()
|
|
32
|
+
|
|
33
|
+
command = [
|
|
34
|
+
"--processes", str(processes),
|
|
35
|
+
"--threads", str(threads),
|
|
36
|
+
# This module does not have broker local. Thus dramatiq fallbacks to
|
|
37
|
+
# global broker.
|
|
38
|
+
__name__,
|
|
39
|
+
]
|
|
40
|
+
if verbose:
|
|
41
|
+
command += ["-v"]
|
|
42
|
+
if current_app.config['DEBUG']:
|
|
43
|
+
command += ["--watch", current_app.root_path]
|
|
44
|
+
|
|
45
|
+
queues = queues.split(",") if queues else []
|
|
46
|
+
if queues:
|
|
47
|
+
command += ["--queues"] + queues
|
|
48
|
+
|
|
49
|
+
args = parser.parse_args(command)
|
|
50
|
+
dramatiq_worker(args)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.command("scheduler")
|
|
54
|
+
@click.option('-v', '--verbose', is_flag=True)
|
|
55
|
+
@with_appcontext
|
|
56
|
+
def scheduler_command(verbose):
|
|
57
|
+
"""Run periodiq scheduler.
|
|
58
|
+
"""
|
|
59
|
+
check_dramatiq_availability()
|
|
60
|
+
|
|
61
|
+
periodic_actors = [
|
|
62
|
+
a for a in current_app.dramatiq_broker.actors.values()
|
|
63
|
+
if 'periodic' in a.options
|
|
64
|
+
]
|
|
65
|
+
if not periodic_actors:
|
|
66
|
+
click.echo("No scheduled Dramatiq actors found.")
|
|
67
|
+
sys.exit(10)
|
|
68
|
+
|
|
69
|
+
command = [
|
|
70
|
+
# This module does not have broker local. Thus dramatiq fallbacks to
|
|
71
|
+
# global broker.
|
|
72
|
+
__name__,
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
if verbose:
|
|
76
|
+
command += ["-v"]
|
|
77
|
+
|
|
78
|
+
parser = periodiq.make_argument_parser()
|
|
79
|
+
args = parser.parse_args(command)
|
|
80
|
+
periodiq.main(args)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def check_dramatiq_availability():
|
|
84
|
+
if not getattr(current_app, 'dramatiq_broker', None):
|
|
85
|
+
click.echo("Dramatiq broker is not configured.")
|
|
86
|
+
sys.exit(10)
|
|
87
|
+
if not current_app.dramatiq_broker.actors:
|
|
88
|
+
click.echo("No Dramatiq actors found.")
|
|
89
|
+
sys.exit(10)
|
|
90
|
+
set_broker(current_app.dramatiq_broker)
|
|
@@ -25,9 +25,10 @@ def discover_components(app, path=None, package_name=None):
|
|
|
25
25
|
|
|
26
26
|
loader = jinjapy.register_package(package_name, path, env=app.jinja_env)
|
|
27
27
|
for module_name, template in loader.list_files(module_with_package=False):
|
|
28
|
+
filename = template or (loader.prefix + module_name.replace(".", os.sep) + ".py")
|
|
28
29
|
adapter_class = None
|
|
29
30
|
for pattern, adapter_import_path in force_adapters.items():
|
|
30
|
-
if re.match(pattern,
|
|
31
|
+
if re.match(pattern, filename):
|
|
31
32
|
adapter_module, adapter_classname = adapter_import_path.split(":")
|
|
32
33
|
adapter_class = getattr(importlib.import_module(adapter_module), adapter_classname)
|
|
33
34
|
break
|
|
@@ -40,8 +41,8 @@ def discover_components(app, path=None, package_name=None):
|
|
|
40
41
|
if not matches:
|
|
41
42
|
continue
|
|
42
43
|
if adapter_class:
|
|
43
|
-
name = os.path.basename(template).split(".")[0].replace("-", "_")
|
|
44
|
-
adapter = adapter_class(name, f"{package_name}.{module_name}" if module_name else None, template)
|
|
44
|
+
name = (os.path.basename(template).split(".")[0] if template else module_name.split('.')[-1]).replace("-", "_")
|
|
45
|
+
adapter = adapter_class(name, f"{package_name}.{module_name}" if module_name else None, template, filename)
|
|
45
46
|
components.append(adapter)
|
|
46
47
|
|
|
47
48
|
return loader, components
|
|
@@ -4,7 +4,7 @@ from .core import ComponentAdapter
|
|
|
4
4
|
class AlpineAdapter(ComponentAdapter):
|
|
5
5
|
@classmethod
|
|
6
6
|
def matches(cls, app, module_name, template):
|
|
7
|
-
if not ComponentAdapter.matches(app, module_name, template):
|
|
7
|
+
if not template or not ComponentAdapter.matches(app, module_name, template):
|
|
8
8
|
return False
|
|
9
9
|
tpl = app.jinja_env.loader.get_source(app.jinja_env, template)[0]
|
|
10
10
|
return any(tag in tpl for tag in ["x-data", "x-init"])
|