plain 0.32.1__tar.gz → 0.33.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.
- plain-0.33.0/PKG-INFO +77 -0
- plain-0.33.0/plain/README.md +66 -0
- {plain-0.32.1 → plain-0.33.0}/plain/assets/README.md +20 -20
- {plain-0.32.1 → plain-0.33.0}/plain/assets/compile.py +8 -11
- {plain-0.32.1 → plain-0.33.0}/plain/assets/finders.py +9 -0
- {plain-0.32.1 → plain-0.33.0}/plain/assets/fingerprints.py +20 -0
- {plain-0.32.1 → plain-0.33.0}/plain/assets/urls.py +9 -0
- {plain-0.32.1 → plain-0.33.0}/plain/assets/views.py +7 -3
- plain-0.33.0/plain/cli/README.md +84 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/core.py +203 -4
- plain-0.33.0/plain/csrf/README.md +18 -0
- plain-0.33.0/plain/forms/README.md +72 -0
- plain-0.33.0/plain/http/README.md +26 -0
- plain-0.33.0/plain/internal/__init__.py +7 -0
- plain-0.33.0/plain/logs/README.md +54 -0
- plain-0.33.0/plain/packages/README.md +75 -0
- plain-0.33.0/plain/preflight/README.md +53 -0
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/__init__.py +2 -2
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/files.py +2 -2
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/messages.py +1 -1
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/registry.py +3 -3
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/security.py +9 -13
- {plain-0.32.1 → plain-0.33.0}/plain/preflight/urls.py +2 -2
- plain-0.33.0/plain/runtime/README.md +158 -0
- {plain-0.32.1 → plain-0.33.0}/plain/runtime/global_settings.py +2 -2
- {plain-0.32.1 → plain-0.33.0}/plain/signals/README.md +1 -1
- plain-0.33.0/plain/templates/README.md +80 -0
- plain-0.33.0/plain/test/README.md +38 -0
- plain-0.33.0/plain/test/__init__.py +6 -0
- {plain-0.32.1 → plain-0.33.0}/plain/test/client.py +104 -247
- plain-0.33.0/plain/test/encoding.py +98 -0
- plain-0.33.0/plain/test/exceptions.py +7 -0
- plain-0.33.0/plain/urls/README.md +150 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/patterns.py +2 -0
- plain-0.33.0/plain/utils/README.md +5 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/README.md +2 -2
- {plain-0.32.1 → plain-0.33.0}/plain/views/base.py +7 -9
- {plain-0.32.1 → plain-0.33.0}/plain/views/forms.py +1 -1
- {plain-0.32.1 → plain-0.33.0}/pyproject.toml +1 -1
- plain-0.32.1/PKG-INFO +0 -12
- plain-0.32.1/plain/README.md +0 -1
- plain-0.32.1/plain/cli/README.md +0 -101
- plain-0.32.1/plain/csrf/README.md +0 -15
- plain-0.32.1/plain/forms/README.md +0 -14
- plain-0.32.1/plain/http/README.md +0 -3
- plain-0.32.1/plain/internal/files/README.md +0 -3
- plain-0.32.1/plain/logs/README.md +0 -24
- plain-0.32.1/plain/packages/README.md +0 -41
- plain-0.32.1/plain/preflight/README.md +0 -5
- plain-0.32.1/plain/runtime/README.md +0 -70
- plain-0.32.1/plain/templates/README.md +0 -20
- plain-0.32.1/plain/templates/jinja/README.md +0 -213
- plain-0.32.1/plain/test/README.md +0 -3
- plain-0.32.1/plain/test/__init__.py +0 -8
- plain-0.32.1/plain/urls/README.md +0 -3
- plain-0.32.1/plain/utils/README.md +0 -3
- plain-0.32.1/tests/app/test/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/.gitignore +0 -0
- {plain-0.32.1 → plain-0.33.0}/LICENSE +0 -0
- {plain-0.32.1 → plain-0.33.0}/README.md +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/__main__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/assets/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/formatting.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/print.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/registry.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/cli/startup.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/csrf/middleware.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/csrf/views.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/debug.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/exceptions.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/forms/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/forms/boundfield.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/forms/exceptions.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/forms/fields.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/forms/forms.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/http/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/http/cookie.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/http/multipartparser.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/http/request.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/http/response.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/base.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/locks.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/move.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/temp.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/uploadedfile.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/uploadhandler.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/files/utils.py +0 -0
- {plain-0.32.1/plain/internal → plain-0.33.0/plain/internal/handlers}/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/handlers/base.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/handlers/exception.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/handlers/wsgi.py +0 -0
- {plain-0.32.1/plain/internal/handlers → plain-0.33.0/plain/internal/middleware}/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/middleware/headers.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/middleware/https.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/internal/middleware/slash.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/json.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/logs/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/logs/configure.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/logs/loggers.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/logs/utils.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/packages/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/packages/config.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/packages/registry.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/paginator.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/runtime/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/runtime/user_settings.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/signals/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/signals/dispatch/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/signals/dispatch/dispatcher.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/signals/dispatch/license.txt +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/signing.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/core.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/jinja/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/jinja/environments.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/jinja/extensions.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/jinja/filters.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/templates/jinja/globals.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/converters.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/exceptions.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/resolvers.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/routers.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/urls/utils.py +0 -0
- {plain-0.32.1/plain/internal/middleware → plain-0.33.0/plain/utils}/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/cache.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/connection.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/crypto.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/datastructures.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/dateparse.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/deconstruct.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/decorators.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/duration.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/encoding.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/functional.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/hashable.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/html.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/http.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/inspect.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/ipv6.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/itercompat.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/module_loading.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/regex_helper.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/safestring.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/text.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/timesince.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/timezone.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/utils/tree.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/validators.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/csrf.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/errors.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/exceptions.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/objects.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/redirect.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/views/templates.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/plain/wsgi.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/.bolt/assets_collected/assets.json +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/.gitignore +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/app/.gitignore +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/app/settings.py +0 -0
- {plain-0.32.1/plain/utils → plain-0.33.0/tests/app/test}/__init__.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/app/test/default_settings.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/app/urls.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/conftest.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/test_cli.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/test_runtime.py +0 -0
- {plain-0.32.1 → plain-0.33.0}/tests/test_wsgi.py +0 -0
plain-0.33.0/PKG-INFO
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: plain
|
3
|
+
Version: 0.33.0
|
4
|
+
Summary: A web framework for building products with Python.
|
5
|
+
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
|
+
License-File: LICENSE
|
7
|
+
Requires-Python: >=3.11
|
8
|
+
Requires-Dist: click>=8.0.0
|
9
|
+
Requires-Dist: jinja2>=3.1.2
|
10
|
+
Description-Content-Type: text/markdown
|
11
|
+
|
12
|
+
# Plain
|
13
|
+
|
14
|
+
**Plain is a web framework for building products with Python.**
|
15
|
+
|
16
|
+
The core `plain` package provides the backbone of a Python web application (similar to [Flask](https://flask.palletsprojects.com/en/stable/)), while the additional first-party packages can power a more fully-featured database-backed app (similar to [Django](https://www.djangoproject.com/)).
|
17
|
+
|
18
|
+
All Plain packages are designed to work together and use [PEP 420](https://peps.python.org/pep-0420/) to share the `plain` namespace.
|
19
|
+
|
20
|
+
To quickly get started with Plain, visit [plainframework.com/start/](https://plainframework.com/start/).
|
21
|
+
|
22
|
+
## Core Modules
|
23
|
+
|
24
|
+
The `plain` package includes everything you need to start handling web requests with Python:
|
25
|
+
|
26
|
+
- [assets](assets/README.md) - Serve static files and assets.
|
27
|
+
- [cli](cli/README.md) - The `plain` CLI, powered by Click.
|
28
|
+
- [csrf](csrf/README.md) - Cross-Site Request Forgery protection.
|
29
|
+
- [forms](forms/README.md) - HTML forms and form validation.
|
30
|
+
- [http](http/README.md) - HTTP request and response handling.
|
31
|
+
- [logs](logs/README.md) - Logging configuration and utilities.
|
32
|
+
- [preflight](preflight/README.md) - Preflight checks for your app.
|
33
|
+
- [runtime](runtime/README.md) - Runtime settings and configuration.
|
34
|
+
- [templates](templates/README.md) - Jinja2 templates and rendering.
|
35
|
+
- [test](test/README.md) - Test utilities and fixtures.
|
36
|
+
- [urls](urls/README.md) - URL routing and request dispatching.
|
37
|
+
- [views](views/README.md) - Class-based views and request handlers.
|
38
|
+
|
39
|
+
## Foundational Packages
|
40
|
+
|
41
|
+
- [plain.models](/plain-models/README.md) - Define and interact with your database models.
|
42
|
+
- [plain.cache](/plain-cache/README.md) - A database-driven general purpose cache.
|
43
|
+
- [plain.mail](/plain-mail/README.md) - Send emails with SMTP or custom backends.
|
44
|
+
- [plain.sessions](/plain-sessions/README.md) - User sessions and cookies.
|
45
|
+
- [plain.worker](/plain-worker/README.md) - Backgrounb jobs stored in the database.
|
46
|
+
- [plain.api](/plain-api/README.md) - Build APIs with Plain views.
|
47
|
+
|
48
|
+
## Auth Packages
|
49
|
+
|
50
|
+
- [plain.auth](/plain-auth/README.md) - User authentication and authorization.
|
51
|
+
- [plain.oauth](/plain-oauth/README.md) - OAuth authentication and API access.
|
52
|
+
- [plain.passwords](/plain-passwords/README.md) - Password-based login and registration.
|
53
|
+
- [plain.loginlink](/plain-loginlink/README.md) - Login links for passwordless authentication.
|
54
|
+
|
55
|
+
## Admin Packages
|
56
|
+
|
57
|
+
- [plain.admin](/plain-admin/README.md) - An admin interface for back-office tasks.
|
58
|
+
- [plain.flags](/plain-flags/README.md) - Feature flags.
|
59
|
+
- [plain.support](/plain-support/README.md) - Customer support forms.
|
60
|
+
- [plain.redirection](/plain-redirection/README.md) - Redirects managed in the database.
|
61
|
+
- [plain.pageviews](/plain-pageviews/README.md) - Basic self-hosted page view tracking and reporting.
|
62
|
+
|
63
|
+
## Dev Packages
|
64
|
+
|
65
|
+
- [plain.dev](/plain-dev/README.md) - A single command for local development.
|
66
|
+
- [plain.pytest](/plain-pytest/README.md) - Pytest fixtures and helpers.
|
67
|
+
- [plain.code](/plain-code/README.md) - Code formatting and linting.
|
68
|
+
- [plain.tunnel](/plain-tunnel/README.md) - Expose your local server to the internet.
|
69
|
+
|
70
|
+
## Frontend Packages
|
71
|
+
|
72
|
+
- [plain.tailwind](/plain-tailwind/README.md) - Tailwind CSS integration without Node.js.
|
73
|
+
- [plain.htmx](/plain-htmx/README.md) - HTMX integrated into views and templates.
|
74
|
+
- [plain.elements](/plain-elements/README.md) - Server-side HTML components.
|
75
|
+
- [plain.pages](/plain-pages/README.md) - Static pages with Markdown and Jinja2.
|
76
|
+
- [plain.esbuild](/plain-esbuild/README.md) - Simple JavaScript bundling and minification.
|
77
|
+
- [plain.vendor](/plain-vendor/README.md) - Vendor JavaScript and CSS libraries.
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Plain
|
2
|
+
|
3
|
+
**Plain is a web framework for building products with Python.**
|
4
|
+
|
5
|
+
The core `plain` package provides the backbone of a Python web application (similar to [Flask](https://flask.palletsprojects.com/en/stable/)), while the additional first-party packages can power a more fully-featured database-backed app (similar to [Django](https://www.djangoproject.com/)).
|
6
|
+
|
7
|
+
All Plain packages are designed to work together and use [PEP 420](https://peps.python.org/pep-0420/) to share the `plain` namespace.
|
8
|
+
|
9
|
+
To quickly get started with Plain, visit [plainframework.com/start/](https://plainframework.com/start/).
|
10
|
+
|
11
|
+
## Core Modules
|
12
|
+
|
13
|
+
The `plain` package includes everything you need to start handling web requests with Python:
|
14
|
+
|
15
|
+
- [assets](assets/README.md) - Serve static files and assets.
|
16
|
+
- [cli](cli/README.md) - The `plain` CLI, powered by Click.
|
17
|
+
- [csrf](csrf/README.md) - Cross-Site Request Forgery protection.
|
18
|
+
- [forms](forms/README.md) - HTML forms and form validation.
|
19
|
+
- [http](http/README.md) - HTTP request and response handling.
|
20
|
+
- [logs](logs/README.md) - Logging configuration and utilities.
|
21
|
+
- [preflight](preflight/README.md) - Preflight checks for your app.
|
22
|
+
- [runtime](runtime/README.md) - Runtime settings and configuration.
|
23
|
+
- [templates](templates/README.md) - Jinja2 templates and rendering.
|
24
|
+
- [test](test/README.md) - Test utilities and fixtures.
|
25
|
+
- [urls](urls/README.md) - URL routing and request dispatching.
|
26
|
+
- [views](views/README.md) - Class-based views and request handlers.
|
27
|
+
|
28
|
+
## Foundational Packages
|
29
|
+
|
30
|
+
- [plain.models](/plain-models/README.md) - Define and interact with your database models.
|
31
|
+
- [plain.cache](/plain-cache/README.md) - A database-driven general purpose cache.
|
32
|
+
- [plain.mail](/plain-mail/README.md) - Send emails with SMTP or custom backends.
|
33
|
+
- [plain.sessions](/plain-sessions/README.md) - User sessions and cookies.
|
34
|
+
- [plain.worker](/plain-worker/README.md) - Backgrounb jobs stored in the database.
|
35
|
+
- [plain.api](/plain-api/README.md) - Build APIs with Plain views.
|
36
|
+
|
37
|
+
## Auth Packages
|
38
|
+
|
39
|
+
- [plain.auth](/plain-auth/README.md) - User authentication and authorization.
|
40
|
+
- [plain.oauth](/plain-oauth/README.md) - OAuth authentication and API access.
|
41
|
+
- [plain.passwords](/plain-passwords/README.md) - Password-based login and registration.
|
42
|
+
- [plain.loginlink](/plain-loginlink/README.md) - Login links for passwordless authentication.
|
43
|
+
|
44
|
+
## Admin Packages
|
45
|
+
|
46
|
+
- [plain.admin](/plain-admin/README.md) - An admin interface for back-office tasks.
|
47
|
+
- [plain.flags](/plain-flags/README.md) - Feature flags.
|
48
|
+
- [plain.support](/plain-support/README.md) - Customer support forms.
|
49
|
+
- [plain.redirection](/plain-redirection/README.md) - Redirects managed in the database.
|
50
|
+
- [plain.pageviews](/plain-pageviews/README.md) - Basic self-hosted page view tracking and reporting.
|
51
|
+
|
52
|
+
## Dev Packages
|
53
|
+
|
54
|
+
- [plain.dev](/plain-dev/README.md) - A single command for local development.
|
55
|
+
- [plain.pytest](/plain-pytest/README.md) - Pytest fixtures and helpers.
|
56
|
+
- [plain.code](/plain-code/README.md) - Code formatting and linting.
|
57
|
+
- [plain.tunnel](/plain-tunnel/README.md) - Expose your local server to the internet.
|
58
|
+
|
59
|
+
## Frontend Packages
|
60
|
+
|
61
|
+
- [plain.tailwind](/plain-tailwind/README.md) - Tailwind CSS integration without Node.js.
|
62
|
+
- [plain.htmx](/plain-htmx/README.md) - HTMX integrated into views and templates.
|
63
|
+
- [plain.elements](/plain-elements/README.md) - Server-side HTML components.
|
64
|
+
- [plain.pages](/plain-pages/README.md) - Static pages with Markdown and Jinja2.
|
65
|
+
- [plain.esbuild](/plain-esbuild/README.md) - Simple JavaScript bundling and minification.
|
66
|
+
- [plain.vendor](/plain-vendor/README.md) - Vendor JavaScript and CSS libraries.
|
@@ -1,38 +1,37 @@
|
|
1
1
|
# Assets
|
2
2
|
|
3
|
-
Serve static assets (CSS, JS, images, etc.) directly from
|
4
|
-
|
3
|
+
**Serve static assets (CSS, JS, images, etc.) directly or from a CDN.**
|
5
4
|
|
6
5
|
## Usage
|
7
6
|
|
8
7
|
To serve assets, put them in `app/assets` or `app/{package}/assets`.
|
9
8
|
|
10
|
-
Then include the `
|
9
|
+
Then include the `AssetsRouter` in your own router, typically under the `assets/` path.
|
11
10
|
|
12
11
|
```python
|
13
12
|
# app/urls.py
|
14
|
-
from plain.urls import
|
15
|
-
|
13
|
+
from plain.assets.urls import AssetsRouter
|
14
|
+
from plain.urls import include, Router
|
16
15
|
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
class AppRouter(Router):
|
18
|
+
namespace = ""
|
19
|
+
urls = [
|
20
|
+
include("assets/", AssetsRouter),
|
21
|
+
# your other routes here...
|
22
|
+
]
|
22
23
|
```
|
23
24
|
|
24
|
-
Now in your template you can use the `asset()` function to get the URL
|
25
|
+
Now in your template you can use the `asset()` function to get the URL, which will output the fully compiled and fingerprinted URL.
|
25
26
|
|
26
27
|
```html
|
27
28
|
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
|
28
29
|
```
|
29
30
|
|
30
|
-
|
31
31
|
## Local development
|
32
32
|
|
33
33
|
When you're working with `settings.DEBUG = True`, the assets will be served directly from their original location. You don't need to run `plain build` or configure anything else.
|
34
34
|
|
35
|
-
|
36
35
|
## Production deployment
|
37
36
|
|
38
37
|
In production, one of your deployment steps should be to compile the assets.
|
@@ -41,11 +40,10 @@ In production, one of your deployment steps should be to compile the assets.
|
|
41
40
|
plain build
|
42
41
|
```
|
43
42
|
|
44
|
-
By default, this generates "fingerprinted" and compressed versions of the assets, which are then served by your app. This means that a file like `main.css` will result in two new files, like `main.d0db67b.css` and `main.d0db67b.css.gz`.
|
43
|
+
By default, this [generates "fingerprinted" and compressed versions of the assets](fingerprints.py#get_file_fingerprint), which are then served by your app. This means that a file like `main.css` will result in two new files, like `main.d0db67b.css` and `main.d0db67b.css.gz`.
|
45
44
|
|
46
45
|
The purpose of fingerprinting the assets is to allow the browser to cache them indefinitely. When the content of the file changes, the fingerprint will change, and the browser will use the newer file. This cuts down on the number of requests that your app has to handle related to assets.
|
47
46
|
|
48
|
-
|
49
47
|
## FAQs
|
50
48
|
|
51
49
|
### How do you reference assets in Python code?
|
@@ -67,10 +65,16 @@ mv .plain/assets/compiled /path/to/your/static
|
|
67
65
|
|
68
66
|
### How do I upload the assets to a CDN?
|
69
67
|
|
70
|
-
The steps for this will vary, but the general idea is to compile them, and then upload the compiled assets.
|
68
|
+
The steps for this will vary, but the general idea is to compile them, and then upload the compiled assets from their [compiled location](compile.py#get_compiled_path).
|
71
69
|
|
72
70
|
```bash
|
71
|
+
# Compile the assets
|
73
72
|
plain build
|
73
|
+
|
74
|
+
# List the newly compiled files
|
75
|
+
ls .plain/assets/compiled
|
76
|
+
|
77
|
+
# Upload the files to your CDN
|
74
78
|
./example-upload-to-cdn-script
|
75
79
|
```
|
76
80
|
|
@@ -81,14 +85,10 @@ Use the `ASSETS_BASE_URL` setting to tell the `{{ asset() }}` template function
|
|
81
85
|
ASSETS_BASE_URL = "https://cdn.example.com/"
|
82
86
|
```
|
83
87
|
|
84
|
-
|
85
88
|
### Why aren't the originals copied to the compiled directory?
|
86
89
|
|
87
90
|
The default behavior is to fingerprint assets, which is an exact copy of the original file but with a different filename. The originals aren't copied over because you should generally always use this fingerprinted path (that automatically uses longer-lived caching).
|
88
91
|
|
89
92
|
If you need the originals for any reason, you can use `plain build --keep-original`, though this will typically be combined with `--no-fingerprint` otherwise the fingerprinted files will still get priority in `{{ asset() }}` template calls.
|
90
93
|
|
91
|
-
|
92
|
-
### What about source maps or imported css files?
|
93
|
-
|
94
|
-
TODO
|
94
|
+
Note that by default, the `ASSETS_REDIRECT_ORIGINAL` setting is `True`, which will redirect requests for the original file to the fingerprinted file.
|
@@ -1,14 +1,11 @@
|
|
1
1
|
import gzip
|
2
|
-
import hashlib
|
3
2
|
import os
|
4
3
|
import shutil
|
5
4
|
|
6
5
|
from plain.runtime import settings
|
7
6
|
|
8
7
|
from .finders import iter_assets
|
9
|
-
from .fingerprints import AssetsFingerprintsManifest
|
10
|
-
|
11
|
-
FINGERPRINT_LENGTH = 7
|
8
|
+
from .fingerprints import AssetsFingerprintsManifest, get_file_fingerprint
|
12
9
|
|
13
10
|
SKIP_COMPRESS_EXTENSIONS = (
|
14
11
|
# Images
|
@@ -46,12 +43,17 @@ SKIP_COMPRESS_EXTENSIONS = (
|
|
46
43
|
def get_compiled_path():
|
47
44
|
"""
|
48
45
|
Get the path at runtime to the compiled assets directory.
|
46
|
+
|
49
47
|
There's no reason currently for this to be a user-facing setting.
|
50
48
|
"""
|
51
49
|
return settings.PLAIN_TEMP_PATH / "assets" / "compiled"
|
52
50
|
|
53
51
|
|
54
52
|
def compile_assets(*, target_dir, keep_original, fingerprint, compress):
|
53
|
+
"""
|
54
|
+
Compile all assets to the target directory and save a JSON manifest
|
55
|
+
mapping the original filenames to the compiled filenames.
|
56
|
+
"""
|
55
57
|
manifest = AssetsFingerprintsManifest()
|
56
58
|
|
57
59
|
for asset in iter_assets():
|
@@ -68,8 +70,7 @@ def compile_assets(*, target_dir, keep_original, fingerprint, compress):
|
|
68
70
|
|
69
71
|
yield url_path, resolved_path, compiled_paths
|
70
72
|
|
71
|
-
|
72
|
-
manifest.save()
|
73
|
+
manifest.save()
|
73
74
|
|
74
75
|
|
75
76
|
def compile_asset(*, asset, target_dir, keep_original, fingerprint, compress):
|
@@ -98,11 +99,7 @@ def compile_asset(*, asset, target_dir, keep_original, fingerprint, compress):
|
|
98
99
|
# Fingerprint it with an md5 hash
|
99
100
|
# (maybe need a setting with fnmatch patterns for files to NOT fingerprint?
|
100
101
|
# that would allow pre-fingerprinted files to be used as-is, and keep source maps etc in tact)
|
101
|
-
|
102
|
-
content = f.read()
|
103
|
-
fingerprint_hash = hashlib.md5(content, usedforsecurity=False).hexdigest()[
|
104
|
-
:FINGERPRINT_LENGTH
|
105
|
-
]
|
102
|
+
fingerprint_hash = get_file_fingerprint(asset.absolute_path)
|
106
103
|
|
107
104
|
fingerprinted_basename = f"{base}.{fingerprint_hash}{extension}"
|
108
105
|
fingerprinted_path = os.path.join(target_dir, fingerprinted_basename)
|
@@ -9,6 +9,11 @@ SKIP_ASSETS = (".DS_Store", ".gitignore")
|
|
9
9
|
|
10
10
|
|
11
11
|
def iter_assets():
|
12
|
+
"""
|
13
|
+
Iterate all valid asset files found in the installed
|
14
|
+
packages and the app itself.
|
15
|
+
"""
|
16
|
+
|
12
17
|
class Asset:
|
13
18
|
def __init__(self, *, url_path, absolute_path):
|
14
19
|
self.url_path = url_path
|
@@ -32,6 +37,10 @@ def iter_assets():
|
|
32
37
|
|
33
38
|
|
34
39
|
def iter_asset_dirs():
|
40
|
+
"""
|
41
|
+
Iterate all directories containing assets, from installed
|
42
|
+
packages and from app/assets.
|
43
|
+
"""
|
35
44
|
# Iterate the installed package assets, in order
|
36
45
|
for pkg in packages_registry.get_package_configs():
|
37
46
|
asset_dir = os.path.join(pkg.path, "assets")
|
@@ -1,10 +1,17 @@
|
|
1
|
+
import hashlib
|
1
2
|
import json
|
2
3
|
from functools import cache
|
3
4
|
|
4
5
|
from plain.runtime import settings
|
5
6
|
|
7
|
+
FINGERPRINT_LENGTH = 7
|
8
|
+
|
6
9
|
|
7
10
|
class AssetsFingerprintsManifest(dict):
|
11
|
+
"""
|
12
|
+
A manifest of original filenames to fingerprinted filenames.
|
13
|
+
"""
|
14
|
+
|
8
15
|
def __init__(self):
|
9
16
|
self.path = settings.PLAIN_TEMP_PATH / "assets" / "fingerprints.json"
|
10
17
|
|
@@ -36,3 +43,16 @@ def get_fingerprinted_url_path(url_path):
|
|
36
43
|
manifest = _get_manifest()
|
37
44
|
if url_path in manifest:
|
38
45
|
return manifest[url_path]
|
46
|
+
|
47
|
+
|
48
|
+
def get_file_fingerprint(file_path):
|
49
|
+
"""
|
50
|
+
Get the fingerprint hash for a file.
|
51
|
+
"""
|
52
|
+
with open(file_path, "rb") as f:
|
53
|
+
content = f.read()
|
54
|
+
fingerprint_hash = hashlib.md5(content, usedforsecurity=False).hexdigest()[
|
55
|
+
:FINGERPRINT_LENGTH
|
56
|
+
]
|
57
|
+
|
58
|
+
return fingerprint_hash
|
@@ -6,6 +6,12 @@ from .views import AssetView
|
|
6
6
|
|
7
7
|
|
8
8
|
class AssetsRouter(Router):
|
9
|
+
"""
|
10
|
+
The router for serving static assets.
|
11
|
+
|
12
|
+
Include this router in your app router if you are serving assets yourself.
|
13
|
+
"""
|
14
|
+
|
9
15
|
namespace = "assets"
|
10
16
|
urls = [
|
11
17
|
path("<path:path>", AssetView, name="asset"),
|
@@ -13,6 +19,9 @@ class AssetsRouter(Router):
|
|
13
19
|
|
14
20
|
|
15
21
|
def get_asset_url(url_path):
|
22
|
+
"""
|
23
|
+
Get the full URL to a given asset path.
|
24
|
+
"""
|
16
25
|
if settings.DEBUG:
|
17
26
|
# In debug, we only ever use the original URL path.
|
18
27
|
resolved_url_path = url_path
|
@@ -16,9 +16,9 @@ from plain.runtime import settings
|
|
16
16
|
from plain.urls import reverse
|
17
17
|
from plain.views import View
|
18
18
|
|
19
|
-
from .compile import
|
19
|
+
from .compile import get_compiled_path
|
20
20
|
from .finders import iter_assets
|
21
|
-
from .fingerprints import get_fingerprinted_url_path
|
21
|
+
from .fingerprints import FINGERPRINT_LENGTH, get_fingerprinted_url_path
|
22
22
|
|
23
23
|
|
24
24
|
class AssetView(View):
|
@@ -28,8 +28,12 @@ class AssetView(View):
|
|
28
28
|
This class could be subclassed to further tweak the responses or behavior.
|
29
29
|
"""
|
30
30
|
|
31
|
+
def __init__(self, asset_path=None):
|
32
|
+
# Allow a path to be passed in AssetView.as_view(path="...")
|
33
|
+
self.asset_path = asset_path
|
34
|
+
|
31
35
|
def get_url_path(self):
|
32
|
-
return self.url_kwargs["path"]
|
36
|
+
return self.asset_path or self.url_kwargs["path"]
|
33
37
|
|
34
38
|
def get(self):
|
35
39
|
url_path = self.get_url_path()
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# CLI
|
2
|
+
|
3
|
+
**The `plain` CLI and how to add your own commands to it.**
|
4
|
+
|
5
|
+
Commands are written using [Click](https://click.palletsprojects.com/en/8.1.x/)
|
6
|
+
(one of Plain's few dependencies),
|
7
|
+
which has been one of those most popular CLI frameworks in Python for a long time.
|
8
|
+
|
9
|
+
## Built-in commands
|
10
|
+
|
11
|
+
### `plain build`
|
12
|
+
|
13
|
+
Compile static assets (used in the deploy/production process).
|
14
|
+
|
15
|
+
Automatically runs `plain tailwind build` if [plain.tailwind](/plain-tailwind/) is installed.
|
16
|
+
|
17
|
+
### `plain create`
|
18
|
+
|
19
|
+
Create a new local package.
|
20
|
+
|
21
|
+
### `plain preflight`
|
22
|
+
|
23
|
+
Run preflight checks to ensure your app is ready to run.
|
24
|
+
|
25
|
+
### `plain run`
|
26
|
+
|
27
|
+
Run a Python script in the context of your app.
|
28
|
+
|
29
|
+
### `plain setting`
|
30
|
+
|
31
|
+
View the runtime value of a named setting.
|
32
|
+
|
33
|
+
### `plain shell`
|
34
|
+
|
35
|
+
Open a Python shell with the Plain loaded.
|
36
|
+
|
37
|
+
To auto-load models or run other code at shell launch,
|
38
|
+
create an `app/shell.py` and it will be imported automatically.
|
39
|
+
|
40
|
+
```python
|
41
|
+
# app/shell.py
|
42
|
+
from app.organizations.models import Organization
|
43
|
+
|
44
|
+
__all__ = [
|
45
|
+
"Organization",
|
46
|
+
]
|
47
|
+
```
|
48
|
+
|
49
|
+
### `plain urls`
|
50
|
+
|
51
|
+
List all the URL patterns in your app.
|
52
|
+
|
53
|
+
### `plain utils generate-secret-key`
|
54
|
+
|
55
|
+
Generate a new secret key for your app, to be used in `settings.SECRET_KEY`.
|
56
|
+
|
57
|
+
## Adding commands
|
58
|
+
|
59
|
+
The `register_cli` decorator can be used to add your own commands to the `plain` CLI.
|
60
|
+
|
61
|
+
```python
|
62
|
+
import click
|
63
|
+
from plain.cli import register_cli
|
64
|
+
|
65
|
+
|
66
|
+
@register_cli("example-subgroup-name")
|
67
|
+
@click.group()
|
68
|
+
def cli():
|
69
|
+
"""Custom example commands"""
|
70
|
+
pass
|
71
|
+
|
72
|
+
@cli.command()
|
73
|
+
def example_command():
|
74
|
+
click.echo("An example command!")
|
75
|
+
```
|
76
|
+
|
77
|
+
Then you can run the command with `plain`.
|
78
|
+
|
79
|
+
```bash
|
80
|
+
$ plain example-subgroup-name example-command
|
81
|
+
An example command!
|
82
|
+
```
|
83
|
+
|
84
|
+
Technically you can register a CLI from anywhere, but typically you will do it in either `app/cli.py` or a package's `<pkg>/cli.py`, as those modules will be autoloaded by Plain.
|