plain 0.35.0__tar.gz → 0.37.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.37.0/PKG-INFO +77 -0
- plain-0.37.0/plain/README.md +66 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/views.py +3 -3
- plain-0.37.0/plain/cli/build.py +107 -0
- plain-0.37.0/plain/cli/core.py +113 -0
- plain-0.37.0/plain/cli/docs.py +211 -0
- plain-0.37.0/plain/cli/preflight.py +127 -0
- plain-0.37.0/plain/cli/scaffold.py +53 -0
- plain-0.37.0/plain/cli/settings.py +60 -0
- plain-0.37.0/plain/cli/shell.py +56 -0
- plain-0.37.0/plain/cli/startup.py +45 -0
- plain-0.37.0/plain/cli/urls.py +87 -0
- plain-0.37.0/plain/cli/utils.py +15 -0
- {plain-0.35.0 → plain-0.37.0}/plain/csrf/README.md +1 -1
- {plain-0.35.0 → plain-0.37.0}/plain/csrf/middleware.py +3 -3
- {plain-0.35.0 → plain-0.37.0}/plain/http/README.md +1 -1
- {plain-0.35.0 → plain-0.37.0}/plain/http/response.py +15 -9
- {plain-0.35.0 → plain-0.37.0}/plain/runtime/global_settings.py +8 -1
- {plain-0.35.0 → plain-0.37.0}/plain/views/README.md +1 -1
- {plain-0.35.0 → plain-0.37.0}/plain/views/base.py +1 -1
- {plain-0.35.0 → plain-0.37.0}/plain/views/objects.py +8 -64
- {plain-0.35.0 → plain-0.37.0}/plain/views/templates.py +11 -9
- {plain-0.35.0 → plain-0.37.0}/pyproject.toml +1 -1
- plain-0.35.0/PKG-INFO +0 -77
- plain-0.35.0/plain/README.md +0 -66
- plain-0.35.0/plain/cli/core.py +0 -781
- plain-0.35.0/plain/cli/startup.py +0 -33
- {plain-0.35.0 → plain-0.37.0}/.gitignore +0 -0
- {plain-0.35.0 → plain-0.37.0}/LICENSE +0 -0
- {plain-0.35.0 → plain-0.37.0}/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/__main__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/compile.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/finders.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/fingerprints.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/assets/urls.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/cli/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/cli/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/cli/formatting.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/cli/print.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/cli/registry.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/csrf/views.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/debug.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/exceptions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/boundfield.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/exceptions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/fields.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/forms/forms.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/http/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/http/cookie.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/http/multipartparser.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/http/request.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/base.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/locks.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/move.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/temp.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/uploadedfile.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/uploadhandler.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/files/utils.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/handlers/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/handlers/base.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/handlers/exception.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/handlers/wsgi.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/middleware/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/middleware/headers.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/middleware/https.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/internal/middleware/slash.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/json.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/logs/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/logs/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/logs/configure.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/logs/loggers.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/logs/utils.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/packages/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/packages/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/packages/config.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/packages/registry.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/paginator.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/files.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/messages.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/registry.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/security.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/preflight/urls.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/runtime/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/runtime/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/runtime/user_settings.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signals/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signals/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signals/dispatch/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signals/dispatch/dispatcher.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signals/dispatch/license.txt +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/signing.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/core.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/jinja/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/jinja/environments.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/jinja/extensions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/jinja/filters.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/templates/jinja/globals.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/test/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/test/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/test/client.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/test/encoding.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/test/exceptions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/converters.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/exceptions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/patterns.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/resolvers.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/routers.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/urls/utils.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/README.md +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/cache.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/connection.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/crypto.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/datastructures.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/dateparse.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/deconstruct.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/decorators.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/duration.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/encoding.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/functional.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/hashable.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/html.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/http.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/inspect.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/ipv6.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/itercompat.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/module_loading.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/regex_helper.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/safestring.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/text.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/timesince.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/timezone.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/utils/tree.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/validators.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/csrf.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/errors.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/exceptions.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/forms.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/views/redirect.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/plain/wsgi.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/.bolt/assets_collected/assets.json +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/.gitignore +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/app/.gitignore +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/app/settings.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/app/test/__init__.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/app/test/default_settings.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/app/urls.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/conftest.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/test_cli.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/test_runtime.py +0 -0
- {plain-0.35.0 → plain-0.37.0}/tests/test_wsgi.py +0 -0
plain-0.37.0/PKG-INFO
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: plain
|
3
|
+
Version: 0.37.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/plain/models/README.md) - Define and interact with your database models.
|
42
|
+
- [plain.cache](/plain-cache/plain/cache/README.md) - A database-driven general purpose cache.
|
43
|
+
- [plain.email](/plain-email/plain/email/README.md) - Send emails with SMTP or custom backends.
|
44
|
+
- [plain.sessions](/plain-sessions/plain/sessions/README.md) - User sessions and cookies.
|
45
|
+
- [plain.worker](/plain-worker/plain/worker/README.md) - Backgrounb jobs stored in the database.
|
46
|
+
- [plain.api](/plain-api/plain/api/README.md) - Build APIs with Plain views.
|
47
|
+
|
48
|
+
## Auth Packages
|
49
|
+
|
50
|
+
- [plain.auth](/plain-auth/plain/auth/README.md) - User authentication and authorization.
|
51
|
+
- [plain.oauth](/plain-oauth/plain/oauth/README.md) - OAuth authentication and API access.
|
52
|
+
- [plain.passwords](/plain-passwords/plain/passwords/README.md) - Password-based login and registration.
|
53
|
+
- [plain.loginlink](/plain-loginlink/plain/loginlink/README.md) - Login links for passwordless authentication.
|
54
|
+
|
55
|
+
## Admin Packages
|
56
|
+
|
57
|
+
- [plain.admin](/plain-admin/plain/admin/README.md) - An admin interface for back-office tasks.
|
58
|
+
- [plain.flags](/plain-flags/plain/flags/README.md) - Feature flags.
|
59
|
+
- [plain.support](/plain-support/plain/support/README.md) - Customer support forms.
|
60
|
+
- [plain.redirection](/plain-redirection/plain/redirection/README.md) - Redirects managed in the database.
|
61
|
+
- [plain.pageviews](/plain-pageviews/plain/pageviews/README.md) - Basic self-hosted page view tracking and reporting.
|
62
|
+
|
63
|
+
## Dev Packages
|
64
|
+
|
65
|
+
- [plain.dev](/plain-dev/plain/dev/README.md) - A single command for local development.
|
66
|
+
- [plain.pytest](/plain-pytest/plain/pytest/README.md) - Pytest fixtures and helpers.
|
67
|
+
- [plain.code](/plain-code/plain/code/README.md) - Code formatting and linting.
|
68
|
+
- [plain.tunnel](/plain-tunnel/plain/tunnel/README.md) - Expose your local server to the internet.
|
69
|
+
|
70
|
+
## Frontend Packages
|
71
|
+
|
72
|
+
- [plain.tailwind](/plain-tailwind/plain/tailwind/README.md) - Tailwind CSS integration without Node.js.
|
73
|
+
- [plain.htmx](/plain-htmx/plain/htmx/README.md) - HTMX integrated into views and templates.
|
74
|
+
- [plain.elements](/plain-elements/plain/elements/README.md) - Server-side HTML components.
|
75
|
+
- [plain.pages](/plain-pages/plain/pages/README.md) - Static pages with Markdown and Jinja2.
|
76
|
+
- [plain.esbuild](/plain-esbuild/plain/esbuild/README.md) - Simple JavaScript bundling and minification.
|
77
|
+
- [plain.vendor](/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/plain/models/README.md) - Define and interact with your database models.
|
31
|
+
- [plain.cache](/plain-cache/plain/cache/README.md) - A database-driven general purpose cache.
|
32
|
+
- [plain.email](/plain-email/plain/email/README.md) - Send emails with SMTP or custom backends.
|
33
|
+
- [plain.sessions](/plain-sessions/plain/sessions/README.md) - User sessions and cookies.
|
34
|
+
- [plain.worker](/plain-worker/plain/worker/README.md) - Backgrounb jobs stored in the database.
|
35
|
+
- [plain.api](/plain-api/plain/api/README.md) - Build APIs with Plain views.
|
36
|
+
|
37
|
+
## Auth Packages
|
38
|
+
|
39
|
+
- [plain.auth](/plain-auth/plain/auth/README.md) - User authentication and authorization.
|
40
|
+
- [plain.oauth](/plain-oauth/plain/oauth/README.md) - OAuth authentication and API access.
|
41
|
+
- [plain.passwords](/plain-passwords/plain/passwords/README.md) - Password-based login and registration.
|
42
|
+
- [plain.loginlink](/plain-loginlink/plain/loginlink/README.md) - Login links for passwordless authentication.
|
43
|
+
|
44
|
+
## Admin Packages
|
45
|
+
|
46
|
+
- [plain.admin](/plain-admin/plain/admin/README.md) - An admin interface for back-office tasks.
|
47
|
+
- [plain.flags](/plain-flags/plain/flags/README.md) - Feature flags.
|
48
|
+
- [plain.support](/plain-support/plain/support/README.md) - Customer support forms.
|
49
|
+
- [plain.redirection](/plain-redirection/plain/redirection/README.md) - Redirects managed in the database.
|
50
|
+
- [plain.pageviews](/plain-pageviews/plain/pageviews/README.md) - Basic self-hosted page view tracking and reporting.
|
51
|
+
|
52
|
+
## Dev Packages
|
53
|
+
|
54
|
+
- [plain.dev](/plain-dev/plain/dev/README.md) - A single command for local development.
|
55
|
+
- [plain.pytest](/plain-pytest/plain/pytest/README.md) - Pytest fixtures and helpers.
|
56
|
+
- [plain.code](/plain-code/plain/code/README.md) - Code formatting and linting.
|
57
|
+
- [plain.tunnel](/plain-tunnel/plain/tunnel/README.md) - Expose your local server to the internet.
|
58
|
+
|
59
|
+
## Frontend Packages
|
60
|
+
|
61
|
+
- [plain.tailwind](/plain-tailwind/plain/tailwind/README.md) - Tailwind CSS integration without Node.js.
|
62
|
+
- [plain.htmx](/plain-htmx/plain/htmx/README.md) - HTMX integrated into views and templates.
|
63
|
+
- [plain.elements](/plain-elements/plain/elements/README.md) - Server-side HTML components.
|
64
|
+
- [plain.pages](/plain-pages/plain/pages/README.md) - Static pages with Markdown and Jinja2.
|
65
|
+
- [plain.esbuild](/plain-esbuild/plain/esbuild/README.md) - Simple JavaScript bundling and minification.
|
66
|
+
- [plain.vendor](/plain-vendor/plain/vendor/README.md) - Vendor JavaScript and CSS libraries.
|
@@ -254,7 +254,7 @@ class AssetView(View):
|
|
254
254
|
|
255
255
|
if not range_header.startswith("bytes="):
|
256
256
|
return Response(
|
257
|
-
|
257
|
+
status_code=416, headers=[("Content-Range", f"bytes */{file_size}")]
|
258
258
|
)
|
259
259
|
|
260
260
|
range_values = range_header.split("=")[1].split("-")
|
@@ -263,7 +263,7 @@ class AssetView(View):
|
|
263
263
|
|
264
264
|
if start >= file_size:
|
265
265
|
return Response(
|
266
|
-
|
266
|
+
status_code=416, headers=[("Content-Range", f"bytes */{file_size}")]
|
267
267
|
)
|
268
268
|
|
269
269
|
end = min(end, file_size - 1)
|
@@ -272,7 +272,7 @@ class AssetView(View):
|
|
272
272
|
f.seek(start)
|
273
273
|
content = f.read(end - start + 1)
|
274
274
|
|
275
|
-
response = StreamingResponse(BytesIO(content),
|
275
|
+
response = StreamingResponse(BytesIO(content), status_code=206)
|
276
276
|
response.headers = self.update_headers(response.headers, path)
|
277
277
|
response.headers["Content-Range"] = f"bytes {start}-{end}/{file_size}"
|
278
278
|
response.headers["Content-Length"] = str(end - start + 1)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import shutil
|
2
|
+
import subprocess
|
3
|
+
import sys
|
4
|
+
import tomllib
|
5
|
+
from importlib.metadata import entry_points
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
import click
|
9
|
+
|
10
|
+
import plain.runtime
|
11
|
+
from plain.assets.compile import compile_assets, get_compiled_path
|
12
|
+
|
13
|
+
|
14
|
+
@click.command()
|
15
|
+
@click.option(
|
16
|
+
"--keep-original/--no-keep-original",
|
17
|
+
"keep_original",
|
18
|
+
is_flag=True,
|
19
|
+
default=False,
|
20
|
+
help="Keep the original assets",
|
21
|
+
)
|
22
|
+
@click.option(
|
23
|
+
"--fingerprint/--no-fingerprint",
|
24
|
+
"fingerprint",
|
25
|
+
is_flag=True,
|
26
|
+
default=True,
|
27
|
+
help="Fingerprint the assets",
|
28
|
+
)
|
29
|
+
@click.option(
|
30
|
+
"--compress/--no-compress",
|
31
|
+
"compress",
|
32
|
+
is_flag=True,
|
33
|
+
default=True,
|
34
|
+
help="Compress the assets",
|
35
|
+
)
|
36
|
+
def build(keep_original, fingerprint, compress):
|
37
|
+
"""Pre-deployment build step (compile assets, css, js, etc.)"""
|
38
|
+
|
39
|
+
if not keep_original and not fingerprint:
|
40
|
+
click.secho(
|
41
|
+
"You must either keep the original assets or fingerprint them.",
|
42
|
+
fg="red",
|
43
|
+
err=True,
|
44
|
+
)
|
45
|
+
sys.exit(1)
|
46
|
+
|
47
|
+
# Run user-defined build commands first
|
48
|
+
pyproject_path = plain.runtime.APP_PATH.parent / "pyproject.toml"
|
49
|
+
if pyproject_path.exists():
|
50
|
+
with pyproject_path.open("rb") as f:
|
51
|
+
pyproject = tomllib.load(f)
|
52
|
+
|
53
|
+
for name, data in (
|
54
|
+
pyproject.get("tool", {})
|
55
|
+
.get("plain", {})
|
56
|
+
.get("build", {})
|
57
|
+
.get("run", {})
|
58
|
+
.items()
|
59
|
+
):
|
60
|
+
click.secho(f"Running {name} from pyproject.toml", bold=True)
|
61
|
+
result = subprocess.run(data["cmd"], shell=True)
|
62
|
+
print()
|
63
|
+
if result.returncode:
|
64
|
+
click.secho(f"Error in {name} (exit {result.returncode})", fg="red")
|
65
|
+
sys.exit(result.returncode)
|
66
|
+
|
67
|
+
# Then run installed package build steps (like tailwind, typically should run last...)
|
68
|
+
for entry_point in entry_points(group="plain.build"):
|
69
|
+
click.secho(f"Running {entry_point.name}", bold=True)
|
70
|
+
result = entry_point.load()()
|
71
|
+
print()
|
72
|
+
|
73
|
+
# Compile our assets
|
74
|
+
target_dir = get_compiled_path()
|
75
|
+
click.secho(f"Compiling assets to {target_dir}", bold=True)
|
76
|
+
if target_dir.exists():
|
77
|
+
click.secho("(clearing previously compiled assets)")
|
78
|
+
shutil.rmtree(target_dir)
|
79
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
80
|
+
|
81
|
+
total_files = 0
|
82
|
+
total_compiled = 0
|
83
|
+
|
84
|
+
for url_path, resolved_url_path, compiled_paths in compile_assets(
|
85
|
+
target_dir=target_dir,
|
86
|
+
keep_original=keep_original,
|
87
|
+
fingerprint=fingerprint,
|
88
|
+
compress=compress,
|
89
|
+
):
|
90
|
+
if url_path == resolved_url_path:
|
91
|
+
click.secho(url_path, bold=True)
|
92
|
+
else:
|
93
|
+
click.secho(url_path, bold=True, nl=False)
|
94
|
+
click.secho(" → ", fg="yellow", nl=False)
|
95
|
+
click.echo(resolved_url_path)
|
96
|
+
|
97
|
+
print("\n".join(f" {Path(p).relative_to(Path.cwd())}" for p in compiled_paths))
|
98
|
+
|
99
|
+
total_files += 1
|
100
|
+
total_compiled += len(compiled_paths)
|
101
|
+
|
102
|
+
click.secho(
|
103
|
+
f"\nCompiled {total_files} assets into {total_compiled} files", fg="green"
|
104
|
+
)
|
105
|
+
|
106
|
+
# TODO could do a jinja pre-compile here too?
|
107
|
+
# environment.compile_templates() but it needs a target, ignore_errors=False
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import traceback
|
2
|
+
|
3
|
+
import click
|
4
|
+
from click.core import Command, Context
|
5
|
+
|
6
|
+
import plain.runtime
|
7
|
+
from plain.exceptions import ImproperlyConfigured
|
8
|
+
|
9
|
+
from .build import build
|
10
|
+
from .docs import docs
|
11
|
+
from .formatting import PlainContext
|
12
|
+
from .preflight import preflight_checks
|
13
|
+
from .registry import cli_registry
|
14
|
+
from .scaffold import create
|
15
|
+
from .settings import setting
|
16
|
+
from .shell import run, shell
|
17
|
+
from .urls import urls
|
18
|
+
from .utils import utils
|
19
|
+
|
20
|
+
|
21
|
+
@click.group()
|
22
|
+
def plain_cli():
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
plain_cli.add_command(docs)
|
27
|
+
plain_cli.add_command(preflight_checks)
|
28
|
+
plain_cli.add_command(create)
|
29
|
+
plain_cli.add_command(build)
|
30
|
+
plain_cli.add_command(utils)
|
31
|
+
plain_cli.add_command(urls)
|
32
|
+
plain_cli.add_command(setting)
|
33
|
+
plain_cli.add_command(shell)
|
34
|
+
plain_cli.add_command(run)
|
35
|
+
|
36
|
+
|
37
|
+
class CLIRegistryGroup(click.Group):
|
38
|
+
"""
|
39
|
+
Click Group that exposes commands from the CLI registry.
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, *args, **kwargs):
|
43
|
+
super().__init__(*args, **kwargs)
|
44
|
+
cli_registry.import_modules()
|
45
|
+
|
46
|
+
def list_commands(self, ctx):
|
47
|
+
return sorted(cli_registry.get_commands().keys())
|
48
|
+
|
49
|
+
def get_command(self, ctx, name):
|
50
|
+
commands = cli_registry.get_commands()
|
51
|
+
return commands.get(name)
|
52
|
+
|
53
|
+
|
54
|
+
class PlainCommandCollection(click.CommandCollection):
|
55
|
+
context_class = PlainContext
|
56
|
+
|
57
|
+
def __init__(self, *args, **kwargs):
|
58
|
+
sources = []
|
59
|
+
|
60
|
+
try:
|
61
|
+
plain.runtime.setup()
|
62
|
+
|
63
|
+
sources = [
|
64
|
+
CLIRegistryGroup(),
|
65
|
+
plain_cli,
|
66
|
+
]
|
67
|
+
except plain.runtime.AppPathNotFound:
|
68
|
+
# Allow some commands to work regardless of being in a valid app
|
69
|
+
click.secho(
|
70
|
+
"Plain `app` directory not found. Some commands may be missing.",
|
71
|
+
fg="yellow",
|
72
|
+
err=True,
|
73
|
+
)
|
74
|
+
|
75
|
+
sources = [
|
76
|
+
plain_cli,
|
77
|
+
]
|
78
|
+
except ImproperlyConfigured as e:
|
79
|
+
# Show what was configured incorrectly and exit
|
80
|
+
click.secho(
|
81
|
+
str(e),
|
82
|
+
fg="red",
|
83
|
+
err=True,
|
84
|
+
)
|
85
|
+
|
86
|
+
exit(1)
|
87
|
+
except Exception as e:
|
88
|
+
# Show the exception and exit
|
89
|
+
print("---")
|
90
|
+
print(traceback.format_exc())
|
91
|
+
print("---")
|
92
|
+
|
93
|
+
click.secho(
|
94
|
+
f"Error: {e}",
|
95
|
+
fg="red",
|
96
|
+
err=True,
|
97
|
+
)
|
98
|
+
|
99
|
+
exit(1)
|
100
|
+
|
101
|
+
super().__init__(*args, **kwargs)
|
102
|
+
|
103
|
+
self.sources = sources
|
104
|
+
|
105
|
+
def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
|
106
|
+
cmd = super().get_command(ctx, cmd_name)
|
107
|
+
if cmd:
|
108
|
+
# Pass the formatting down to subcommands automatically
|
109
|
+
cmd.context_class = self.context_class
|
110
|
+
return cmd
|
111
|
+
|
112
|
+
|
113
|
+
cli = PlainCommandCollection()
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import ast
|
2
|
+
import importlib.util
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
from plain.packages import packages_registry
|
9
|
+
|
10
|
+
|
11
|
+
def symbolicate(file_path: Path):
|
12
|
+
if "internal" in str(file_path).split("/"):
|
13
|
+
return ""
|
14
|
+
|
15
|
+
source = file_path.read_text()
|
16
|
+
|
17
|
+
parsed = ast.parse(source)
|
18
|
+
|
19
|
+
def should_skip(node):
|
20
|
+
if isinstance(node, ast.ClassDef | ast.FunctionDef):
|
21
|
+
if any(
|
22
|
+
isinstance(d, ast.Name) and d.id == "internalcode"
|
23
|
+
for d in node.decorator_list
|
24
|
+
):
|
25
|
+
return True
|
26
|
+
if node.name.startswith("_"): # and not node.name.endswith("__"):
|
27
|
+
return True
|
28
|
+
elif isinstance(node, ast.Assign):
|
29
|
+
for target in node.targets:
|
30
|
+
if (
|
31
|
+
isinstance(target, ast.Name) and target.id.startswith("_")
|
32
|
+
# and not target.id.endswith("__")
|
33
|
+
):
|
34
|
+
return True
|
35
|
+
return False
|
36
|
+
|
37
|
+
def process_node(node, indent=0):
|
38
|
+
lines = []
|
39
|
+
prefix = " " * indent
|
40
|
+
|
41
|
+
if should_skip(node):
|
42
|
+
return []
|
43
|
+
|
44
|
+
if isinstance(node, ast.ClassDef):
|
45
|
+
decorators = [
|
46
|
+
f"{prefix}@{ast.unparse(d)}"
|
47
|
+
for d in node.decorator_list
|
48
|
+
if not (isinstance(d, ast.Name) and d.id == "internal")
|
49
|
+
]
|
50
|
+
lines.extend(decorators)
|
51
|
+
bases = [ast.unparse(base) for base in node.bases]
|
52
|
+
lines.append(f"{prefix}class {node.name}({', '.join(bases)})")
|
53
|
+
# if ast.get_docstring(node):
|
54
|
+
# lines.append(f'{prefix} """{ast.get_docstring(node)}"""')
|
55
|
+
for child in node.body:
|
56
|
+
child_lines = process_node(child, indent + 1)
|
57
|
+
if child_lines:
|
58
|
+
lines.extend(child_lines)
|
59
|
+
# if not has_body:
|
60
|
+
# lines.append(f"{prefix} pass")
|
61
|
+
|
62
|
+
elif isinstance(node, ast.FunctionDef):
|
63
|
+
decorators = [f"{prefix}@{ast.unparse(d)}" for d in node.decorator_list]
|
64
|
+
lines.extend(decorators)
|
65
|
+
args = ast.unparse(node.args)
|
66
|
+
lines.append(f"{prefix}def {node.name}({args})")
|
67
|
+
# if ast.get_docstring(node):
|
68
|
+
# lines.append(f'{prefix} """{ast.get_docstring(node)}"""')
|
69
|
+
# lines.append(f"{prefix} pass")
|
70
|
+
|
71
|
+
elif isinstance(node, ast.Assign):
|
72
|
+
for target in node.targets:
|
73
|
+
if isinstance(target, ast.Name):
|
74
|
+
lines.append(f"{prefix}{target.id} = {ast.unparse(node.value)}")
|
75
|
+
|
76
|
+
return lines
|
77
|
+
|
78
|
+
symbolicated_lines = []
|
79
|
+
for node in parsed.body:
|
80
|
+
symbolicated_lines.extend(process_node(node))
|
81
|
+
|
82
|
+
return "\n".join(symbolicated_lines)
|
83
|
+
|
84
|
+
|
85
|
+
@click.command()
|
86
|
+
@click.option("--llm", "llm", is_flag=True)
|
87
|
+
@click.option("--open")
|
88
|
+
@click.argument("module", default="")
|
89
|
+
def docs(module, llm, open):
|
90
|
+
if not module and not llm:
|
91
|
+
click.secho("You must specify a module or use --llm", fg="red")
|
92
|
+
sys.exit(1)
|
93
|
+
|
94
|
+
if llm:
|
95
|
+
click.echo(
|
96
|
+
"Below is all of the documentation and abbreviated source code for the Plain web framework. "
|
97
|
+
"Your job is to read and understand it, and then act as the Plain Framework Assistant and "
|
98
|
+
"help the developer accomplish whatever they want to do next."
|
99
|
+
"\n\n---\n\n"
|
100
|
+
)
|
101
|
+
|
102
|
+
docs = set()
|
103
|
+
sources = set()
|
104
|
+
|
105
|
+
# Get everything for Plain core
|
106
|
+
for path in Path(__file__).parent.parent.glob("**/*.md"):
|
107
|
+
docs.add(path)
|
108
|
+
for source in Path(__file__).parent.parent.glob("**/*.py"):
|
109
|
+
sources.add(source)
|
110
|
+
|
111
|
+
# Find every *.md file in the other plain packages and installed apps
|
112
|
+
for package_config in packages_registry.get_package_configs():
|
113
|
+
if package_config.name.startswith("app."):
|
114
|
+
# Ignore app packages for now
|
115
|
+
continue
|
116
|
+
|
117
|
+
for path in Path(package_config.path).glob("**/*.md"):
|
118
|
+
docs.add(path)
|
119
|
+
|
120
|
+
for source in Path(package_config.path).glob("**/*.py"):
|
121
|
+
sources.add(source)
|
122
|
+
|
123
|
+
docs = sorted(docs)
|
124
|
+
sources = sorted(sources)
|
125
|
+
|
126
|
+
for doc in docs:
|
127
|
+
try:
|
128
|
+
display_path = doc.relative_to(Path.cwd())
|
129
|
+
except ValueError:
|
130
|
+
display_path = doc.absolute()
|
131
|
+
click.secho(f"<Docs: {display_path}>", fg="yellow")
|
132
|
+
click.echo(doc.read_text())
|
133
|
+
click.secho(f"</Docs: {display_path}>", fg="yellow")
|
134
|
+
click.echo()
|
135
|
+
|
136
|
+
for source in sources:
|
137
|
+
if symbolicated := symbolicate(source):
|
138
|
+
try:
|
139
|
+
display_path = source.relative_to(Path.cwd())
|
140
|
+
except ValueError:
|
141
|
+
display_path = source.absolute()
|
142
|
+
click.secho(f"<Source: {display_path}>", fg="yellow")
|
143
|
+
click.echo(symbolicated)
|
144
|
+
click.secho(f"</Source: {display_path}>", fg="yellow")
|
145
|
+
click.echo()
|
146
|
+
|
147
|
+
click.secho(
|
148
|
+
"That's everything! Copy this into your AI tool of choice.",
|
149
|
+
err=True,
|
150
|
+
fg="green",
|
151
|
+
)
|
152
|
+
|
153
|
+
return
|
154
|
+
|
155
|
+
if module:
|
156
|
+
# Automatically prefix if we need to
|
157
|
+
if not module.startswith("plain"):
|
158
|
+
module = f"plain.{module}"
|
159
|
+
|
160
|
+
# Get the README.md file for the module
|
161
|
+
spec = importlib.util.find_spec(module)
|
162
|
+
if not spec:
|
163
|
+
click.secho(f"Module {module} not found", fg="red")
|
164
|
+
sys.exit(1)
|
165
|
+
|
166
|
+
module_path = Path(spec.origin).parent
|
167
|
+
readme_path = module_path / "README.md"
|
168
|
+
if not readme_path.exists():
|
169
|
+
click.secho(f"README.md not found for {module}", fg="red")
|
170
|
+
sys.exit(1)
|
171
|
+
|
172
|
+
if open:
|
173
|
+
click.launch(str(readme_path))
|
174
|
+
else:
|
175
|
+
|
176
|
+
def _iterate_markdown(content):
|
177
|
+
"""
|
178
|
+
Iterator that does basic markdown for a Click pager.
|
179
|
+
|
180
|
+
Headings are yellow and bright, code blocks are indented.
|
181
|
+
"""
|
182
|
+
|
183
|
+
in_code_block = False
|
184
|
+
for line in content.splitlines():
|
185
|
+
if line.startswith("```"):
|
186
|
+
in_code_block = not in_code_block
|
187
|
+
|
188
|
+
if in_code_block:
|
189
|
+
yield click.style(line, dim=True)
|
190
|
+
elif line.startswith("# "):
|
191
|
+
yield click.style(line, fg="yellow", bold=True)
|
192
|
+
elif line.startswith("## "):
|
193
|
+
yield click.style(line, fg="yellow", bold=True)
|
194
|
+
elif line.startswith("### "):
|
195
|
+
yield click.style(line, fg="yellow", bold=True)
|
196
|
+
elif line.startswith("#### "):
|
197
|
+
yield click.style(line, fg="yellow", bold=True)
|
198
|
+
elif line.startswith("##### "):
|
199
|
+
yield click.style(line, fg="yellow", bold=True)
|
200
|
+
elif line.startswith("###### "):
|
201
|
+
yield click.style(line, fg="yellow", bold=True)
|
202
|
+
elif line.startswith("**") and line.endswith("**"):
|
203
|
+
yield click.style(line, bold=True)
|
204
|
+
elif line.startswith("> "):
|
205
|
+
yield click.style(line, italic=True)
|
206
|
+
else:
|
207
|
+
yield line
|
208
|
+
|
209
|
+
yield "\n"
|
210
|
+
|
211
|
+
click.echo_via_pager(_iterate_markdown(readme_path.read_text()))
|