hyperflask 0.5.2__tar.gz → 0.7.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.5.2 → hyperflask-0.7.0}/PKG-INFO +7 -8
- {hyperflask-0.5.2 → hyperflask-0.7.0}/README.md +5 -5
- {hyperflask-0.5.2 → hyperflask-0.7.0}/pyproject.toml +5 -3
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/__init__.py +3 -0
- hyperflask-0.7.0/src/hyperflask/actors.py +128 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/app.py +42 -31
- hyperflask-0.7.0/src/hyperflask/cli/__init__.py +135 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/cli/runner.py +57 -39
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/cli/worker.py +5 -2
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/components/core.py +23 -2
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/factory.py +8 -5
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/layouts/web.html +2 -2
- hyperflask-0.5.2/src/hyperflask/model.py → hyperflask-0.7.0/src/hyperflask/models.py +2 -22
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/app.css +2 -0
- hyperflask-0.7.0/src/hyperflask/static/app.js +11 -0
- hyperflask-0.7.0/src/hyperflask/static/htmx-exts.js +186 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/forms.html +3 -2
- hyperflask-0.7.0/src/hyperflask/ui/sse.html +24 -0
- hyperflask-0.7.0/src/hyperflask/utils/collections.py +10 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/htmx.py +6 -11
- hyperflask-0.5.2/src/hyperflask/actors.py +0 -82
- hyperflask-0.5.2/src/hyperflask/cli/__init__.py +0 -46
- hyperflask-0.5.2/src/hyperflask/static/app.js +0 -14
- hyperflask-0.5.2/src/hyperflask/static/htmx-exts.js +0 -128
- hyperflask-0.5.2/src/hyperflask/ui/sse.html +0 -22
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/__main__.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/cli/reloader.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/cli/scaffold.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/components/__init__.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/components/alpine.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/components/jsx.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/components/webcomponent.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/forms/__init__.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/forms/fields.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/forms/widgets.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/layouts/email.mjml +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/page_helpers.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/security.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/alpine-csp.js +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/alpine.js +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/react.js +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/stimulus.js +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/static/translations.js +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Accordion.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/AccordionPanel.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Alert.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Avatar.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/AvatarGroup.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Badge.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Breadcrumbs.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/BreadcrumbsItem.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Button.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Card.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/CardActions.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/CardTitle.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/ChatBubble.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Checkbox.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/CollapsibleSubMenu.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Countdown.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Divider.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Drawer.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/DrawerContent.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/DrawerSide.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Dropdown.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/DropdownItem.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Fieldset.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/FieldsetLegend.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/FileInput.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Filter.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/FilterItem.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Footer.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/FooterLink.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/FooterNav.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Indicator.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Input.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/InputLabel.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Join.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Kbd.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Label.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/LabeledCheckbox.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/LabeledToggle.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Link.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/List.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/ListItem.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Loading.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Menu.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/MenuItem.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/MenuTitle.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Modal.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/ModalAction.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/ModalTitle.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Navbar.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Pagination.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/PaginationNext.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/PaginationPage.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/PaginationPrev.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Progress.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/README.md +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Radio.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Range.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Rating.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Select.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Stack.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Stat.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Stats.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Step.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Steps.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/SubMenu.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Swap.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Tab.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Table.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Tabs.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Textarea.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Toast.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Toggle.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/daisyui/Tooltip.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/flash.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/icons.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/layout.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/modal.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/ui/skeleton.html +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/__init__.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/freezer.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/html.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/image.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/markdown.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/metadata.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/page_actions.py +0 -0
- {hyperflask-0.5.2 → hyperflask-0.7.0}/src/hyperflask/utils/tokens.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperflask
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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>
|
|
7
7
|
License-Expression: MIT
|
|
8
|
-
Requires-Dist: flask>=3.
|
|
8
|
+
Requires-Dist: flask>=3.1.0,<4.0.0
|
|
9
9
|
Requires-Dist: flask-assets-pipeline~=0.8
|
|
10
10
|
Requires-Dist: flask-babel-hyper~=5.1
|
|
11
11
|
Requires-Dist: flask-collections~=0.3
|
|
@@ -13,7 +13,6 @@ Requires-Dist: flask-configurator~=0.1
|
|
|
13
13
|
Requires-Dist: flask-debugtoolbar~=0.16
|
|
14
14
|
Requires-Dist: flask-file-routes~=0.3
|
|
15
15
|
Requires-Dist: flask-files~=0.2
|
|
16
|
-
Requires-Dist: flask-geo~=0.1
|
|
17
16
|
Requires-Dist: flask-mailman-templates~=0.1
|
|
18
17
|
Requires-Dist: flask-mercure-sse~=0.4
|
|
19
18
|
Requires-Dist: flask-sqlorm~=0.3
|
|
@@ -43,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
43
42
|
|
|
44
43
|
**Full stack Python web framework to build websites and web apps with as little boilerplate as possible**
|
|
45
44
|
|
|
46
|
-
[hyperflask.dev](https://hyperflask.dev) • [Get started](https://hyperflask.dev/getting-started) • [Docs](https://hyperflask.dev/guides) • [Example
|
|
45
|
+
[hyperflask.dev](https://hyperflask.dev) • [Get started](https://hyperflask.dev/getting-started) • [Docs](https://hyperflask.dev/guides) • [Example app](https://github.com/hyperflask/getting-started)
|
|
47
46
|
|
|
48
47
|
</div>
|
|
49
48
|
|
|
@@ -74,11 +73,10 @@ Features and technologies:
|
|
|
74
73
|
- Dev environment based on [Development Containers](https://containers.dev/)
|
|
75
74
|
- Optimized for VScode with breakpoint debugging for frontend and backend
|
|
76
75
|
- [Mailpit](https://github.com/axllent/mailpit) for email testing
|
|
77
|
-
- Deployments using [docker-web-deploy](https://github.com/hyperflask/docker-web-deploy)
|
|
78
76
|
|
|
79
77
|
## Project status
|
|
80
78
|
|
|
81
|
-
**Hyperflask is
|
|
79
|
+
**Hyperflask is in BETA and can start to be used for non-critical production apps**
|
|
82
80
|
|
|
83
81
|
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).
|
|
84
82
|
|
|
@@ -116,5 +114,6 @@ A good part of these extensions is developed as part of the Hyperflask project.
|
|
|
116
114
|
1. Create your project directory: `mkdir example-project && cd example-project`
|
|
117
115
|
2. Create and activate a virtualenv: `python -m venv .venv && source .venv/bin/activate`
|
|
118
116
|
3. `pip install hyperflask`
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
5. Run `npm install esbuild tailwindcss @tailwindcss/cli htmx.org@4 bootstrap-icons daisyui`
|
|
118
|
+
4. Run `mkdir pages && echo "Hello world" > pages/index.jpy`
|
|
119
|
+
6. Start a development server using `hyperflask dev`
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Full stack Python web framework to build websites and web apps with as little boilerplate as possible**
|
|
6
6
|
|
|
7
|
-
[hyperflask.dev](https://hyperflask.dev) • [Get started](https://hyperflask.dev/getting-started) • [Docs](https://hyperflask.dev/guides) • [Example
|
|
7
|
+
[hyperflask.dev](https://hyperflask.dev) • [Get started](https://hyperflask.dev/getting-started) • [Docs](https://hyperflask.dev/guides) • [Example app](https://github.com/hyperflask/getting-started)
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
@@ -35,11 +35,10 @@ Features and technologies:
|
|
|
35
35
|
- Dev environment based on [Development Containers](https://containers.dev/)
|
|
36
36
|
- Optimized for VScode with breakpoint debugging for frontend and backend
|
|
37
37
|
- [Mailpit](https://github.com/axllent/mailpit) for email testing
|
|
38
|
-
- Deployments using [docker-web-deploy](https://github.com/hyperflask/docker-web-deploy)
|
|
39
38
|
|
|
40
39
|
## Project status
|
|
41
40
|
|
|
42
|
-
**Hyperflask is
|
|
41
|
+
**Hyperflask is in BETA and can start to be used for non-critical production apps**
|
|
43
42
|
|
|
44
43
|
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).
|
|
45
44
|
|
|
@@ -77,5 +76,6 @@ A good part of these extensions is developed as part of the Hyperflask project.
|
|
|
77
76
|
1. Create your project directory: `mkdir example-project && cd example-project`
|
|
78
77
|
2. Create and activate a virtualenv: `python -m venv .venv && source .venv/bin/activate`
|
|
79
78
|
3. `pip install hyperflask`
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
5. Run `npm install esbuild tailwindcss @tailwindcss/cli htmx.org@4 bootstrap-icons daisyui`
|
|
80
|
+
4. Run `mkdir pages && echo "Hello world" > pages/index.jpy`
|
|
81
|
+
6. Start a development server using `hyperflask dev`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hyperflask"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Full stack web framework"
|
|
5
5
|
authors = [
|
|
6
6
|
{"name" = "Maxime Bouroumeau-Fuseau", email = "maxime.bouroumeau@gmail.com"}
|
|
@@ -9,7 +9,7 @@ readme = "README.md"
|
|
|
9
9
|
license = "MIT"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"flask>=3.
|
|
12
|
+
"flask>=3.1.0,<4.0.0",
|
|
13
13
|
"flask-assets-pipeline ~=0.8",
|
|
14
14
|
"flask-babel-hyper ~=5.1",
|
|
15
15
|
"flask-collections ~=0.3",
|
|
@@ -17,7 +17,6 @@ dependencies = [
|
|
|
17
17
|
"flask-debugtoolbar ~=0.16",
|
|
18
18
|
"flask-file-routes ~=0.3",
|
|
19
19
|
"flask-files ~=0.2",
|
|
20
|
-
"flask-geo ~=0.1",
|
|
21
20
|
"flask-mailman-templates ~=0.1",
|
|
22
21
|
"flask-mercure-sse ~=0.4",
|
|
23
22
|
#"flask-observability ~=0.1"
|
|
@@ -49,6 +48,9 @@ Repository = "https://github.com/hyperflask/hyperflask"
|
|
|
49
48
|
[project.scripts]
|
|
50
49
|
hyperflask = "hyperflask.cli:main"
|
|
51
50
|
|
|
51
|
+
[project.entry-points."flask_collections"]
|
|
52
|
+
model = "hyperflask.model:ModelCollection"
|
|
53
|
+
|
|
52
54
|
[dependency-groups]
|
|
53
55
|
dev = [
|
|
54
56
|
"pytest>=8.4.1",
|
|
@@ -3,6 +3,8 @@ from .app import Hyperflask
|
|
|
3
3
|
from .utils.htmx import htmx_redirect, htmx_oob
|
|
4
4
|
from .utils.freezer import StaticMode, static, dynamic
|
|
5
5
|
from .security import csp_nonce
|
|
6
|
+
from .actors import actor, ActorGroup
|
|
7
|
+
from .models import Model
|
|
6
8
|
|
|
7
9
|
# useful exports from hyperflask managed extensions
|
|
8
10
|
from flask_babel import gettext, _, pgettext, _p, ngettext, _n, npgettext, _np, lazy_gettext, _lazy
|
|
@@ -18,3 +20,4 @@ from htmx_flask import request, make_response
|
|
|
18
20
|
from slugify import slugify
|
|
19
21
|
from blinker import signal
|
|
20
22
|
from periodiq import cron
|
|
23
|
+
from sqlorm import transaction
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from threading import local
|
|
2
|
+
import typing as t
|
|
3
|
+
import importlib
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from dramatiq import Middleware, actor as register_actor, Message
|
|
6
|
+
from flask import has_app_context, current_app
|
|
7
|
+
from sqlorm.helpers import after_commit as sqlorm_after_commit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DRAMATIQ_BROKERS = {
|
|
11
|
+
"amqp": "dramatiq.brokers.rabbitmq:RabbitmqBroker",
|
|
12
|
+
"redis": "dramatiq.brokers.redis:RedisBroker",
|
|
13
|
+
"sqlite": "dramatiq_sqlite:SQLiteBroker",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def discover_broker(broker_cls=None, broker_url=None):
|
|
18
|
+
if not broker_url and broker_cls and "://" in broker_cls:
|
|
19
|
+
broker_url = broker_cls
|
|
20
|
+
broker_cls = None
|
|
21
|
+
if not broker_cls and broker_url:
|
|
22
|
+
if "://" not in broker_url:
|
|
23
|
+
broker_name = "sqlite"
|
|
24
|
+
else:
|
|
25
|
+
broker_name, broker_url = broker_url.split("://", 1)
|
|
26
|
+
if broker_name not in DRAMATIQ_BROKERS:
|
|
27
|
+
raise ValueError(f"Unknown dramatiq broker '{broker_name}'")
|
|
28
|
+
broker_cls = DRAMATIQ_BROKERS[broker_name]
|
|
29
|
+
if isinstance(broker_cls, str):
|
|
30
|
+
module_name, cls_name = broker_cls.split(":")
|
|
31
|
+
module = importlib.import_module(module_name)
|
|
32
|
+
broker_cls = getattr(module, cls_name)
|
|
33
|
+
return broker_cls, broker_url
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class actor:
|
|
37
|
+
@classmethod
|
|
38
|
+
def bind(cls, broker):
|
|
39
|
+
return type('bound_actor', (cls,), {'__broker__': broker})
|
|
40
|
+
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
self.actor_args = (args, kwargs)
|
|
43
|
+
self.fn = None
|
|
44
|
+
self.actor = None
|
|
45
|
+
|
|
46
|
+
def __call__(self, *args, **kwargs):
|
|
47
|
+
if not self.fn:
|
|
48
|
+
self.fn = args[0]
|
|
49
|
+
if getattr(self, '__broker__', None):
|
|
50
|
+
self.register(self.__broker__)
|
|
51
|
+
elif has_app_context() and getattr(current_app, 'dramatiq_broker', None):
|
|
52
|
+
self.register(current_app.dramatiq_broker)
|
|
53
|
+
return self
|
|
54
|
+
return self.fn(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
def register(self, broker):
|
|
57
|
+
self.actor = register_actor(broker=broker, *self.actor_args[0], **self.actor_args[1])(self.fn)
|
|
58
|
+
|
|
59
|
+
def __getattr__(self, name):
|
|
60
|
+
if self.actor:
|
|
61
|
+
return getattr(self.actor, name)
|
|
62
|
+
raise AttributeError(name)
|
|
63
|
+
|
|
64
|
+
def send(self, *args, **kwargs):
|
|
65
|
+
return self.send_with_options(args=args, kwargs=kwargs)
|
|
66
|
+
|
|
67
|
+
def send_with_options(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
args: tuple = (),
|
|
71
|
+
kwargs: t.Optional[dict[str, t.Any]] = None,
|
|
72
|
+
delay: t.Optional[timedelta | int] = None,
|
|
73
|
+
after_commit: bool = True,
|
|
74
|
+
**options,
|
|
75
|
+
) -> Message:
|
|
76
|
+
if not self.actor:
|
|
77
|
+
raise RuntimeError("No broker defined for Dramatiq actors.")
|
|
78
|
+
|
|
79
|
+
if isinstance(delay, timedelta):
|
|
80
|
+
delay = int(delay.total_seconds() * 1000)
|
|
81
|
+
|
|
82
|
+
message = self.actor.message_with_options(args=args, kwargs=kwargs, **options)
|
|
83
|
+
|
|
84
|
+
if after_commit:
|
|
85
|
+
sqlorm_after_commit(lambda: self.actor.broker.enqueue(message, delay=delay))
|
|
86
|
+
return message
|
|
87
|
+
|
|
88
|
+
return self.actor.broker.enqueue(message, delay=delay)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ActorGroup(list):
|
|
92
|
+
def actor(self, *args, **kwargs):
|
|
93
|
+
def decorator(fn):
|
|
94
|
+
a = actor(*args, **kwargs)(fn)
|
|
95
|
+
self.append(a)
|
|
96
|
+
return a
|
|
97
|
+
return decorator
|
|
98
|
+
|
|
99
|
+
def register(self, broker):
|
|
100
|
+
for a in self:
|
|
101
|
+
a.register(broker)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class AppContextMiddleware(Middleware):
|
|
105
|
+
# Setup Flask app for actor. Borrowed from
|
|
106
|
+
# https://github.com/Bogdanp/flask_dramatiq_example.
|
|
107
|
+
|
|
108
|
+
state = local()
|
|
109
|
+
|
|
110
|
+
def __init__(self, app):
|
|
111
|
+
self.app = app
|
|
112
|
+
|
|
113
|
+
def before_process_message(self, broker, message):
|
|
114
|
+
context = self.app.app_context()
|
|
115
|
+
context.push()
|
|
116
|
+
|
|
117
|
+
self.state.context = context
|
|
118
|
+
|
|
119
|
+
def after_process_message(
|
|
120
|
+
self, broker, message, *, result=None, exception=None):
|
|
121
|
+
try:
|
|
122
|
+
context = self.state.context
|
|
123
|
+
context.pop(exception)
|
|
124
|
+
del self.state.context
|
|
125
|
+
except AttributeError:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
after_skip_message = after_process_message
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# flask
|
|
2
|
-
from flask import Flask, has_request_context, request
|
|
2
|
+
from flask import Flask, has_request_context, request, current_app
|
|
3
|
+
from flask.globals import _cv_app
|
|
3
4
|
from jinja2 import ChoiceLoader, FileSystemLoader, PrefixLoader, TemplateNotFound
|
|
4
5
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
5
6
|
# hyperflask extensions
|
|
@@ -11,12 +12,11 @@ from flask_collections import Collections
|
|
|
11
12
|
from flask_file_routes import FileRoutes, ModuleView, page
|
|
12
13
|
from flask_configurator import Config
|
|
13
14
|
from flask_babel import Babel, _, _p, _n, _np, lazy_gettext
|
|
14
|
-
from flask_geo import Geolocation
|
|
15
15
|
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_after_render_macros
|
|
20
20
|
#from flask_observability import Observability
|
|
21
21
|
# 3rd party extensions
|
|
22
22
|
from flask_wtf.csrf import CSRFProtect
|
|
@@ -33,13 +33,14 @@ from .utils.metadata import metadata_tags
|
|
|
33
33
|
from .utils.freezer import StaticMode, Freezer, freezer_url_generator
|
|
34
34
|
from .utils.image import image_tag
|
|
35
35
|
from .components import register_components
|
|
36
|
-
from .
|
|
37
|
-
from .actors import AppContextMiddleware, discover_broker,
|
|
36
|
+
from .models import Model, File as SQLFileType, UndefinedDatabase
|
|
37
|
+
from .actors import AppContextMiddleware, discover_broker, actor
|
|
38
38
|
from .security import respond_with_security_headers, csp_nonce
|
|
39
39
|
from . import page_helpers
|
|
40
40
|
# others
|
|
41
41
|
from jinja_super_macros.registry import FileLoader
|
|
42
42
|
from jinja_wtforms.extractor import map_jinja_call_node_to_func
|
|
43
|
+
from sqlorm import transaction
|
|
43
44
|
from sqlorm.sql_template import SQLTemplate
|
|
44
45
|
from periodiq import PeriodiqMiddleware
|
|
45
46
|
import os
|
|
@@ -77,12 +78,11 @@ class Hyperflask(Flask):
|
|
|
77
78
|
"SESSION_COOKIE_SAMESITE": "Lax",
|
|
78
79
|
"SQLORM_URI": database_uri,
|
|
79
80
|
# Hyperflask specific
|
|
80
|
-
"URL_FOR_SERVER_NAME": None,
|
|
81
81
|
"LAYOUT": "layouts/default.html",
|
|
82
82
|
"ALPINE": False,
|
|
83
83
|
"STATIC_MODE": static_mode,
|
|
84
84
|
"ASSETS_INCLUDE_ALPINE": False,
|
|
85
|
-
"
|
|
85
|
+
"HTMX_CONFIG": {},
|
|
86
86
|
"HTMX_BOOST_SITE": False,
|
|
87
87
|
"MARKDOWN_OPTIONS": {"extensions": ["fenced_code", "nl2br", "attr_list", "admonition", "codehilite"]},
|
|
88
88
|
"MARKDOWN_SANITIZER_CONFIG": {},
|
|
@@ -111,6 +111,8 @@ class Hyperflask(Flask):
|
|
|
111
111
|
self.config["PAGES_MARKDOWN_OPTIONS"] = self.config["MARKDOWN_OPTIONS"]
|
|
112
112
|
self.config["COLLECTIONS_MARKDOWN_OPTIONS"] = self.config["MARKDOWN_OPTIONS"]
|
|
113
113
|
self.config["MAIL_TEMPLATES_MARKDOWN_OPTIONS"] = self.config["MARKDOWN_OPTIONS"]
|
|
114
|
+
# error responses are not swapped (only out of band swaps are processed)
|
|
115
|
+
self.config["HTMX_CONFIG"] = {"noSwap": [204, 304, "4xx", "5xx"], **self.config["HTMX_CONFIG"]}
|
|
114
116
|
|
|
115
117
|
if self.config['SERVER_SECURED']:
|
|
116
118
|
self.config.update({
|
|
@@ -151,7 +153,7 @@ class Hyperflask(Flask):
|
|
|
151
153
|
PrefixLoader({"ui": FileSystemLoader(os.path.join(os.path.dirname(__file__), "ui"))})
|
|
152
154
|
])
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
HyperDebugToolbarExtension(self)
|
|
155
157
|
self.freezer = Freezer(self, with_no_argument_rules=False, log_url_for=self.config.get('FREEZER_LOG_URL_FOR', False))
|
|
156
158
|
self.freezer.register_generator(lambda: freezer_url_generator(self))
|
|
157
159
|
self.csrf = CSRFProtect(self)
|
|
@@ -161,7 +163,6 @@ class Hyperflask(Flask):
|
|
|
161
163
|
SuperMacros(self)
|
|
162
164
|
self.components = self.macros # alias
|
|
163
165
|
Babel(self, extract_locale_from_request="locale")
|
|
164
|
-
Geolocation(self)
|
|
165
166
|
Files(self)
|
|
166
167
|
MailTemplates(self, template_folder=emails_folder)
|
|
167
168
|
file_routes = FileRoutes(self, pages_folder=pages_folder, module_view_class=HyperModuleView)
|
|
@@ -235,7 +236,9 @@ class Hyperflask(Flask):
|
|
|
235
236
|
self.dramatiq_broker = broker_cls(url=broker_url)
|
|
236
237
|
self.dramatiq_broker.add_middleware(AppContextMiddleware(self))
|
|
237
238
|
self.dramatiq_broker.add_middleware(PeriodiqMiddleware())
|
|
238
|
-
|
|
239
|
+
self.actor = actor.bind(self.dramatiq_broker)
|
|
240
|
+
else:
|
|
241
|
+
self.actor = actor
|
|
239
242
|
|
|
240
243
|
Suspense(self, nonce_getter="csp_nonce()")
|
|
241
244
|
|
|
@@ -243,10 +246,11 @@ class Hyperflask(Flask):
|
|
|
243
246
|
def on_suspense_before_render_template(sender, **kwargs):
|
|
244
247
|
csp_nonce() # we will need a nonce but the first call will be after the template started rendering
|
|
245
248
|
|
|
246
|
-
@
|
|
247
|
-
def
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
@suspense_after_render_macros.connect_via(self, weak=False)
|
|
250
|
+
def on_suspense_after_render_macros(sender, **kwargs):
|
|
251
|
+
# process the content inserted by suspense macros during htmx requests
|
|
252
|
+
# (during full page loads, htmx is not loaded yet and processes the whole page once loaded)
|
|
253
|
+
return f'<script nonce="{csp_nonce()}">window.htmx?.process(document.body)</script>'
|
|
250
254
|
|
|
251
255
|
@self.errorhandler(404)
|
|
252
256
|
def not_found(error):
|
|
@@ -281,32 +285,39 @@ class Hyperflask(Flask):
|
|
|
281
285
|
scheme = self.config.get('PREFERRED_URL_SCHEME', 'http')
|
|
282
286
|
if self.config.get('SERVER_NAME'):
|
|
283
287
|
return f"{scheme}://{self.config['SERVER_NAME']}"
|
|
284
|
-
if self.config['URL_FOR_SERVER_NAME']:
|
|
285
|
-
return f"{scheme}://{self.config['URL_FOR_SERVER_NAME']}"
|
|
286
288
|
if has_request_context():
|
|
287
289
|
return request.host_url
|
|
288
|
-
raise RuntimeError("No request context and no SERVER_NAME
|
|
290
|
+
raise RuntimeError("No request context and no SERVER_NAME configured, cannot determine base_url")
|
|
289
291
|
|
|
290
292
|
def url_for(self, *args, **kwargs):
|
|
291
293
|
_external = kwargs.get("_external", False)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
294
|
+
if not has_request_context() and not _external and not self.config.get('SERVER_NAME'):
|
|
295
|
+
self.config['SERVER_NAME'] = 'http://localhost:5000'
|
|
296
|
+
app_ctx = _cv_app.get(None)
|
|
297
|
+
if app_ctx is not None:
|
|
298
|
+
prev_url_adapter = app_ctx.url_adapter
|
|
299
|
+
app_ctx.url_adapter = self.create_url_adapter(None)
|
|
300
|
+
url = super().url_for(*args, **kwargs)
|
|
301
|
+
if app_ctx is not None:
|
|
302
|
+
app_ctx.url_adapter = prev_url_adapter
|
|
303
|
+
url = url[:len(self.config['SERVER_NAME']):]
|
|
304
|
+
self.config['SERVER_NAME'] = None
|
|
301
305
|
return url
|
|
302
|
-
|
|
303
|
-
_scheme = self.config.get('PREFERRED_URL_SCHEME', 'http')
|
|
304
|
-
url = f"{_scheme}://{self.config['URL_FOR_SERVER_NAME']}{url}"
|
|
305
|
-
return url
|
|
306
|
+
return super().url_for(*args, **kwargs)
|
|
306
307
|
|
|
307
308
|
|
|
308
309
|
class HyperModuleView(ModuleView):
|
|
309
|
-
module_globals = dict(ModuleView.module_globals,
|
|
310
|
+
module_globals = dict(ModuleView.module_globals,
|
|
311
|
+
asset_url=asset_url,
|
|
312
|
+
static_url=static_url,
|
|
313
|
+
transaction=transaction)
|
|
310
314
|
|
|
311
315
|
def _render_template(self):
|
|
312
316
|
return render_template(page.template)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class HyperDebugToolbarExtension(DebugToolbarExtension):
|
|
320
|
+
def _show_toolbar(self):
|
|
321
|
+
if current_app.config.get("DEBUG_TB_ENABLED", True) is False:
|
|
322
|
+
return False
|
|
323
|
+
return super()._show_toolbar()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import click
|
|
4
|
+
from flask import current_app
|
|
5
|
+
from flask.cli import FlaskGroup, shell_command, routes_command, ScriptInfo, NoAppException
|
|
6
|
+
from flask_assets_pipeline import cli as assets_cli
|
|
7
|
+
from ..factory import create_app
|
|
8
|
+
from ..security import generate_csp_policy
|
|
9
|
+
from ..utils.freezer import StaticMode
|
|
10
|
+
from .runner import serve_command, run_command, dev_command
|
|
11
|
+
from .worker import worker_command, scheduler_command
|
|
12
|
+
from .scaffold import gen
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _create_app():
|
|
16
|
+
try:
|
|
17
|
+
if os.environ.get("FLASK_APP") == "0":
|
|
18
|
+
raise NoAppException("")
|
|
19
|
+
info = ScriptInfo(os.environ.get("FLASK_APP"))
|
|
20
|
+
return info.load_app()
|
|
21
|
+
except NoAppException:
|
|
22
|
+
return create_app(os.environ.get("FLASK_ROOT"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
cli = FlaskGroup(name='kantree', help='Kantree', add_default_commands=False, create_app=_create_app)
|
|
26
|
+
cli.add_command(shell_command)
|
|
27
|
+
cli.add_command(routes_command)
|
|
28
|
+
cli.add_command(serve_command)
|
|
29
|
+
cli.add_command(run_command)
|
|
30
|
+
cli.add_command(dev_command)
|
|
31
|
+
cli.add_command(worker_command)
|
|
32
|
+
cli.add_command(scheduler_command)
|
|
33
|
+
cli.add_command(gen)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@cli.command("build")
|
|
37
|
+
@click.pass_context
|
|
38
|
+
def build_command(ctx):
|
|
39
|
+
ctx.invoke(assets_cli.build)
|
|
40
|
+
if StaticMode(current_app.config['STATIC_MODE']) != StaticMode.DYNAMIC:
|
|
41
|
+
current_app.freezer.freeze()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cli.command("csp-header")
|
|
45
|
+
def csp_header_command():
|
|
46
|
+
click.echo(generate_csp_policy())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@cli.command("encode-session-cookie")
|
|
50
|
+
@click.argument("data")
|
|
51
|
+
def encode_session_cookie_command(data):
|
|
52
|
+
"""Encode a session cookie value from JSON data"""
|
|
53
|
+
serializer = current_app.session_interface.get_signing_serializer(current_app)
|
|
54
|
+
value = serializer.dumps(json.loads(data))
|
|
55
|
+
click.echo(value)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@cli.command("decode-session-cookie")
|
|
59
|
+
@click.argument("cookie", required=False)
|
|
60
|
+
@click.option("--cookie-file", "-f", type=click.Path(exists=True), help="Path to a file containing a list of cookies (from test-request command)")
|
|
61
|
+
def decode_session_cookie_command(cookie, cookie_file):
|
|
62
|
+
"""Decode a session cookie value to JSON data"""
|
|
63
|
+
if not cookie and cookie_file:
|
|
64
|
+
with open(cookie_file, "r") as f:
|
|
65
|
+
for cookie_data in f.readlines():
|
|
66
|
+
key, value = cookie_data.split(";", 1)[0].split("=", 1)
|
|
67
|
+
if key.strip() == current_app.config["SESSION_COOKIE_NAME"]:
|
|
68
|
+
cookie = value.strip()
|
|
69
|
+
break
|
|
70
|
+
elif not cookie:
|
|
71
|
+
click.echo("Either COOKIE argument or --cookie-file option must be provided", err=True)
|
|
72
|
+
return
|
|
73
|
+
try:
|
|
74
|
+
serializer = current_app.session_interface.get_signing_serializer(current_app)
|
|
75
|
+
data = serializer.loads(cookie)
|
|
76
|
+
click.echo(json.dumps(data, indent=2))
|
|
77
|
+
except Exception as e:
|
|
78
|
+
click.echo("Cannot decode cookie value", err=True)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@cli.command("test-request")
|
|
83
|
+
@click.argument("path")
|
|
84
|
+
@click.option("--method", "-m", default="GET", help="HTTP method to use")
|
|
85
|
+
@click.option("--data", "-d", default=None, help="Data to send with the request")
|
|
86
|
+
@click.option("--header", "-h", multiple=True, help="Headers to include in the request")
|
|
87
|
+
@click.option("--follow-redirects/--no-follow-redirects", default=True, help="Whether to follow redirects")
|
|
88
|
+
@click.option("--session", "-s", help="JSON data that will be used to populate the session cookie")
|
|
89
|
+
@click.option("--cookie", "-c", multiple=True, help="Cookies to include in the request")
|
|
90
|
+
@click.option("--cookie-file", "-f", type=click.Path(), help="Path to read and write cookies from, helpful for preserving cookies between requests")
|
|
91
|
+
@click.pass_context
|
|
92
|
+
def test_request_command(ctx, path, method, data, header, follow_redirects, cookie, session, cookie_file):
|
|
93
|
+
"""Simulate a request to the application and display the response. The path can include query parameters.
|
|
94
|
+
"""
|
|
95
|
+
current_app.config["DEBUG_TB_ENABLED"] = False
|
|
96
|
+
client = current_app.test_client()
|
|
97
|
+
headers = []
|
|
98
|
+
cookies = {}
|
|
99
|
+
for header_ in header:
|
|
100
|
+
key, value = header_.split(":", 1)
|
|
101
|
+
headers.append((key.strip(), value.strip()))
|
|
102
|
+
if cookie_file and os.path.exists(cookie_file):
|
|
103
|
+
with open(cookie_file, "r") as f:
|
|
104
|
+
for cookie_data in f.readlines():
|
|
105
|
+
key, value = cookie_data.split(";", 1)[0].split("=", 1)
|
|
106
|
+
cookies[key.strip()] = value.strip()
|
|
107
|
+
client.set_cookie(key=key.strip(), value=value.strip())
|
|
108
|
+
if cookie:
|
|
109
|
+
for cookie_ in cookie:
|
|
110
|
+
key, value = cookie_.split("=", 1)
|
|
111
|
+
cookies[key.strip()] = value.strip()
|
|
112
|
+
client.set_cookie(key=key.strip(), value=value.strip())
|
|
113
|
+
if session:
|
|
114
|
+
serializer = current_app.session_interface.get_signing_serializer(current_app)
|
|
115
|
+
session_cookie = serializer.dumps(json.loads(session))
|
|
116
|
+
client.set_cookie(key=current_app.config["SESSION_COOKIE_NAME"], value=session_cookie)
|
|
117
|
+
|
|
118
|
+
resp = client.open(path=path, method=method, data=data, headers=headers, follow_redirects=follow_redirects)
|
|
119
|
+
|
|
120
|
+
click.echo(f"HTTP/1.1 {resp.status}")
|
|
121
|
+
for key, value in resp.headers.items():
|
|
122
|
+
click.echo(f"{key}: {value}")
|
|
123
|
+
if key.lower() == "set-cookie":
|
|
124
|
+
name, value = value.split(";", 1)[0].split("=", 1)
|
|
125
|
+
cookies[name.strip()] = value.strip()
|
|
126
|
+
click.echo("")
|
|
127
|
+
click.echo(resp.get_data(as_text=True))
|
|
128
|
+
if cookie_file and cookies:
|
|
129
|
+
with open(cookie_file, "w") as f:
|
|
130
|
+
for name, value in cookies.items():
|
|
131
|
+
f.write(f"{name}={value}\n")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main():
|
|
135
|
+
cli.main()
|