plain 0.1.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.1.0/LICENSE +85 -0
- plain-0.1.0/PKG-INFO +51 -0
- plain-0.1.0/README.md +35 -0
- plain-0.1.0/plain/README.md +33 -0
- plain-0.1.0/plain/__main__.py +5 -0
- plain-0.1.0/plain/assets/README.md +56 -0
- plain-0.1.0/plain/assets/__init__.py +6 -0
- plain-0.1.0/plain/assets/finders.py +233 -0
- plain-0.1.0/plain/assets/preflight.py +14 -0
- plain-0.1.0/plain/assets/storage.py +916 -0
- plain-0.1.0/plain/assets/utils.py +52 -0
- plain-0.1.0/plain/assets/whitenoise/__init__.py +5 -0
- plain-0.1.0/plain/assets/whitenoise/base.py +259 -0
- plain-0.1.0/plain/assets/whitenoise/compress.py +189 -0
- plain-0.1.0/plain/assets/whitenoise/media_types.py +137 -0
- plain-0.1.0/plain/assets/whitenoise/middleware.py +197 -0
- plain-0.1.0/plain/assets/whitenoise/responders.py +286 -0
- plain-0.1.0/plain/assets/whitenoise/storage.py +178 -0
- plain-0.1.0/plain/assets/whitenoise/string_utils.py +13 -0
- plain-0.1.0/plain/cli/README.md +123 -0
- plain-0.1.0/plain/cli/__init__.py +3 -0
- plain-0.1.0/plain/cli/cli.py +439 -0
- plain-0.1.0/plain/cli/formatting.py +61 -0
- plain-0.1.0/plain/cli/packages.py +73 -0
- plain-0.1.0/plain/cli/print.py +9 -0
- plain-0.1.0/plain/cli/startup.py +33 -0
- plain-0.1.0/plain/csrf/README.md +3 -0
- plain-0.1.0/plain/csrf/middleware.py +466 -0
- plain-0.1.0/plain/csrf/views.py +10 -0
- plain-0.1.0/plain/debug.py +23 -0
- plain-0.1.0/plain/exceptions.py +242 -0
- plain-0.1.0/plain/forms/README.md +14 -0
- plain-0.1.0/plain/forms/__init__.py +8 -0
- plain-0.1.0/plain/forms/boundfield.py +58 -0
- plain-0.1.0/plain/forms/exceptions.py +11 -0
- plain-0.1.0/plain/forms/fields.py +1030 -0
- plain-0.1.0/plain/forms/forms.py +297 -0
- plain-0.1.0/plain/http/README.md +1 -0
- plain-0.1.0/plain/http/__init__.py +51 -0
- plain-0.1.0/plain/http/cookie.py +20 -0
- plain-0.1.0/plain/http/multipartparser.py +743 -0
- plain-0.1.0/plain/http/request.py +754 -0
- plain-0.1.0/plain/http/response.py +719 -0
- plain-0.1.0/plain/internal/__init__.py +0 -0
- plain-0.1.0/plain/internal/files/README.md +3 -0
- plain-0.1.0/plain/internal/files/__init__.py +3 -0
- plain-0.1.0/plain/internal/files/base.py +161 -0
- plain-0.1.0/plain/internal/files/locks.py +127 -0
- plain-0.1.0/plain/internal/files/move.py +102 -0
- plain-0.1.0/plain/internal/files/temp.py +79 -0
- plain-0.1.0/plain/internal/files/uploadedfile.py +150 -0
- plain-0.1.0/plain/internal/files/uploadhandler.py +254 -0
- plain-0.1.0/plain/internal/files/utils.py +78 -0
- plain-0.1.0/plain/internal/handlers/__init__.py +0 -0
- plain-0.1.0/plain/internal/handlers/base.py +133 -0
- plain-0.1.0/plain/internal/handlers/exception.py +145 -0
- plain-0.1.0/plain/internal/handlers/wsgi.py +216 -0
- plain-0.1.0/plain/internal/legacy/__init__.py +0 -0
- plain-0.1.0/plain/internal/legacy/__main__.py +12 -0
- plain-0.1.0/plain/internal/legacy/management/__init__.py +414 -0
- plain-0.1.0/plain/internal/legacy/management/base.py +692 -0
- plain-0.1.0/plain/internal/legacy/management/color.py +113 -0
- plain-0.1.0/plain/internal/legacy/management/commands/__init__.py +0 -0
- plain-0.1.0/plain/internal/legacy/management/commands/collectstatic.py +297 -0
- plain-0.1.0/plain/internal/legacy/management/sql.py +67 -0
- plain-0.1.0/plain/internal/legacy/management/utils.py +175 -0
- plain-0.1.0/plain/json.py +40 -0
- plain-0.1.0/plain/logs/README.md +24 -0
- plain-0.1.0/plain/logs/__init__.py +5 -0
- plain-0.1.0/plain/logs/configure.py +39 -0
- plain-0.1.0/plain/logs/loggers.py +74 -0
- plain-0.1.0/plain/logs/utils.py +46 -0
- plain-0.1.0/plain/middleware/README.md +3 -0
- plain-0.1.0/plain/middleware/__init__.py +0 -0
- plain-0.1.0/plain/middleware/clickjacking.py +52 -0
- plain-0.1.0/plain/middleware/common.py +87 -0
- plain-0.1.0/plain/middleware/gzip.py +64 -0
- plain-0.1.0/plain/middleware/security.py +64 -0
- plain-0.1.0/plain/packages/README.md +41 -0
- plain-0.1.0/plain/packages/__init__.py +4 -0
- plain-0.1.0/plain/packages/config.py +259 -0
- plain-0.1.0/plain/packages/registry.py +438 -0
- plain-0.1.0/plain/paginator.py +187 -0
- plain-0.1.0/plain/preflight/README.md +3 -0
- plain-0.1.0/plain/preflight/__init__.py +38 -0
- plain-0.1.0/plain/preflight/compatibility/__init__.py +0 -0
- plain-0.1.0/plain/preflight/compatibility/django_4_0.py +20 -0
- plain-0.1.0/plain/preflight/files.py +19 -0
- plain-0.1.0/plain/preflight/messages.py +88 -0
- plain-0.1.0/plain/preflight/registry.py +72 -0
- plain-0.1.0/plain/preflight/security/__init__.py +0 -0
- plain-0.1.0/plain/preflight/security/base.py +268 -0
- plain-0.1.0/plain/preflight/security/csrf.py +40 -0
- plain-0.1.0/plain/preflight/urls.py +117 -0
- plain-0.1.0/plain/runtime/README.md +75 -0
- plain-0.1.0/plain/runtime/__init__.py +61 -0
- plain-0.1.0/plain/runtime/global_settings.py +199 -0
- plain-0.1.0/plain/runtime/user_settings.py +353 -0
- plain-0.1.0/plain/signals/README.md +14 -0
- plain-0.1.0/plain/signals/__init__.py +5 -0
- plain-0.1.0/plain/signals/dispatch/__init__.py +9 -0
- plain-0.1.0/plain/signals/dispatch/dispatcher.py +320 -0
- plain-0.1.0/plain/signals/dispatch/license.txt +35 -0
- plain-0.1.0/plain/signing.py +299 -0
- plain-0.1.0/plain/templates/README.md +20 -0
- plain-0.1.0/plain/templates/__init__.py +6 -0
- plain-0.1.0/plain/templates/core.py +24 -0
- plain-0.1.0/plain/templates/jinja/README.md +227 -0
- plain-0.1.0/plain/templates/jinja/__init__.py +22 -0
- plain-0.1.0/plain/templates/jinja/defaults.py +119 -0
- plain-0.1.0/plain/templates/jinja/extensions.py +39 -0
- plain-0.1.0/plain/templates/jinja/filters.py +28 -0
- plain-0.1.0/plain/templates/jinja/globals.py +19 -0
- plain-0.1.0/plain/test/README.md +3 -0
- plain-0.1.0/plain/test/__init__.py +16 -0
- plain-0.1.0/plain/test/client.py +985 -0
- plain-0.1.0/plain/test/utils.py +255 -0
- plain-0.1.0/plain/urls/README.md +3 -0
- plain-0.1.0/plain/urls/__init__.py +40 -0
- plain-0.1.0/plain/urls/base.py +118 -0
- plain-0.1.0/plain/urls/conf.py +94 -0
- plain-0.1.0/plain/urls/converters.py +66 -0
- plain-0.1.0/plain/urls/exceptions.py +9 -0
- plain-0.1.0/plain/urls/resolvers.py +731 -0
- plain-0.1.0/plain/utils/README.md +3 -0
- plain-0.1.0/plain/utils/__init__.py +0 -0
- plain-0.1.0/plain/utils/_os.py +52 -0
- plain-0.1.0/plain/utils/cache.py +327 -0
- plain-0.1.0/plain/utils/connection.py +84 -0
- plain-0.1.0/plain/utils/crypto.py +76 -0
- plain-0.1.0/plain/utils/datastructures.py +345 -0
- plain-0.1.0/plain/utils/dateformat.py +329 -0
- plain-0.1.0/plain/utils/dateparse.py +154 -0
- plain-0.1.0/plain/utils/dates.py +76 -0
- plain-0.1.0/plain/utils/deconstruct.py +54 -0
- plain-0.1.0/plain/utils/decorators.py +90 -0
- plain-0.1.0/plain/utils/deprecation.py +6 -0
- plain-0.1.0/plain/utils/duration.py +44 -0
- plain-0.1.0/plain/utils/email.py +12 -0
- plain-0.1.0/plain/utils/encoding.py +235 -0
- plain-0.1.0/plain/utils/functional.py +456 -0
- plain-0.1.0/plain/utils/hashable.py +26 -0
- plain-0.1.0/plain/utils/html.py +401 -0
- plain-0.1.0/plain/utils/http.py +374 -0
- plain-0.1.0/plain/utils/inspect.py +73 -0
- plain-0.1.0/plain/utils/ipv6.py +46 -0
- plain-0.1.0/plain/utils/itercompat.py +8 -0
- plain-0.1.0/plain/utils/module_loading.py +69 -0
- plain-0.1.0/plain/utils/regex_helper.py +353 -0
- plain-0.1.0/plain/utils/safestring.py +72 -0
- plain-0.1.0/plain/utils/termcolors.py +221 -0
- plain-0.1.0/plain/utils/text.py +518 -0
- plain-0.1.0/plain/utils/timesince.py +138 -0
- plain-0.1.0/plain/utils/timezone.py +244 -0
- plain-0.1.0/plain/utils/tree.py +126 -0
- plain-0.1.0/plain/validators.py +603 -0
- plain-0.1.0/plain/views/README.md +268 -0
- plain-0.1.0/plain/views/__init__.py +18 -0
- plain-0.1.0/plain/views/base.py +107 -0
- plain-0.1.0/plain/views/csrf.py +24 -0
- plain-0.1.0/plain/views/errors.py +25 -0
- plain-0.1.0/plain/views/exceptions.py +4 -0
- plain-0.1.0/plain/views/forms.py +76 -0
- plain-0.1.0/plain/views/objects.py +229 -0
- plain-0.1.0/plain/views/redirect.py +72 -0
- plain-0.1.0/plain/views/templates.py +66 -0
- plain-0.1.0/plain/wsgi.py +11 -0
- plain-0.1.0/pyproject.toml +36 -0
plain-0.1.0/LICENSE
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
## Plain is released under the BSD 3-Clause License
|
2
|
+
|
3
|
+
BSD 3-Clause License
|
4
|
+
|
5
|
+
Copyright (c) 2023, Dropseed, LLC
|
6
|
+
|
7
|
+
Redistribution and use in source and binary forms, with or without
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
9
|
+
|
10
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
11
|
+
list of conditions and the following disclaimer.
|
12
|
+
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
15
|
+
and/or other materials provided with the distribution.
|
16
|
+
|
17
|
+
3. Neither the name of the copyright holder nor the names of its
|
18
|
+
contributors may be used to endorse or promote products derived from
|
19
|
+
this software without specific prior written permission.
|
20
|
+
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
22
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
23
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
24
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
25
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
26
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
27
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
28
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
29
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
|
32
|
+
|
33
|
+
## This package contains code forked from github.com/django/django
|
34
|
+
|
35
|
+
Copyright (c) Django Software Foundation and individual contributors.
|
36
|
+
All rights reserved.
|
37
|
+
|
38
|
+
Redistribution and use in source and binary forms, with or without modification,
|
39
|
+
are permitted provided that the following conditions are met:
|
40
|
+
|
41
|
+
1. Redistributions of source code must retain the above copyright notice,
|
42
|
+
this list of conditions and the following disclaimer.
|
43
|
+
|
44
|
+
2. Redistributions in binary form must reproduce the above copyright
|
45
|
+
notice, this list of conditions and the following disclaimer in the
|
46
|
+
documentation and/or other materials provided with the distribution.
|
47
|
+
|
48
|
+
3. Neither the name of Django nor the names of its contributors may be used
|
49
|
+
to endorse or promote products derived from this software without
|
50
|
+
specific prior written permission.
|
51
|
+
|
52
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
53
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
54
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
55
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
56
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
57
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
58
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
59
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
60
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
61
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
62
|
+
|
63
|
+
|
64
|
+
## This package contains code forked from github.com/evansd/whitenoise
|
65
|
+
|
66
|
+
The MIT License (MIT)
|
67
|
+
|
68
|
+
Copyright (c) 2013 David Evans
|
69
|
+
|
70
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
71
|
+
this software and associated documentation files (the "Software"), to deal in
|
72
|
+
the Software without restriction, including without limitation the rights to
|
73
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
74
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
75
|
+
subject to the following conditions:
|
76
|
+
|
77
|
+
The above copyright notice and this permission notice shall be included in all
|
78
|
+
copies or substantial portions of the Software.
|
79
|
+
|
80
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
81
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
82
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
83
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
84
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
85
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
plain-0.1.0/PKG-INFO
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: plain
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A web framework for building products with Python.
|
5
|
+
Author: Dave Gaeddert
|
6
|
+
Author-email: dave.gaeddert@dropseed.dev
|
7
|
+
Requires-Python: >=3.11,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
11
|
+
Requires-Dist: click (>=8.0.0)
|
12
|
+
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
13
|
+
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
|
16
|
+
<!-- This file is compiled from plain/plain/README.md. Do not edit this file directly. -->
|
17
|
+
|
18
|
+
# Plain
|
19
|
+
|
20
|
+
Plain is a web framework for building products with Python.
|
21
|
+
|
22
|
+
With the core `plain` package you can build an app that:
|
23
|
+
|
24
|
+
- Matches [URL patterns](https://plainframework.com/docs/plain/plain/urls) to Python [views](https://plainframework.com/docs/plain/plain/views)
|
25
|
+
- Handles [HTTP requests and responses](https://plainframework.com/docs/plain/plain/http)
|
26
|
+
- Renders [HTML templates](https://plainframework.com/docs/plain/plain/templates) with Jinja
|
27
|
+
- Processes user input via [forms](https://plainframework.com/docs/plain/plain/forms)
|
28
|
+
- Has a [CLI interface](https://plainframework.com/docs/plain/plain/cli)
|
29
|
+
- Serves static [assets](https://plainframework.com/docs/plain/plain/assets) (CSS, JS, images)
|
30
|
+
- Can be modified with [middleware](https://plainframework.com/docs/plain/plain/middleware)
|
31
|
+
- Integrates first-party and third-party [packages](https://plainframework.com/docs/plain/plain/packages)
|
32
|
+
- Has a [preflight check system](https://plainframework.com/docs/plain/plain/preflight)
|
33
|
+
|
34
|
+
With the official Plain ecosystem packages you can:
|
35
|
+
|
36
|
+
- Integrate a full-featured [database ORM](https://plainframework.com/docs/plain-models/)
|
37
|
+
- Use a built-in [user authentication](https://plainframework.com/docs/plain-auth/) system
|
38
|
+
- [Lint and format code](https://plainframework.com/docs/plain-code/)
|
39
|
+
- Run a [database-backed cache](https://plainframework.com/docs/plain-cache/)
|
40
|
+
- [Send emails](https://plainframework.com/docs/plain-mail/)
|
41
|
+
- Streamline [local development](https://plainframework.com/docs/plain-dev/)
|
42
|
+
- Manage [feature flags](https://plainframework.com/docs/plain-flags/)
|
43
|
+
- Integrate [HTMX](https://plainframework.com/docs/plain-htmx/)
|
44
|
+
- Style with [Tailwind CSS](https://plainframework.com/docs/plain-tailwind/)
|
45
|
+
- Add [OAuth login](https://plainframework.com/docs/plain-oauth/) and API access
|
46
|
+
- Run tests with [pytest](https://plainframework.com/docs/plain-test/)
|
47
|
+
- Run a [background job worker](https://plainframework.com/docs/plain-worker/)
|
48
|
+
- Build [staff tooling and admin dashboards](https://plainframework.com/docs/plain-staff/)
|
49
|
+
|
50
|
+
Learn more at [plainframework.com](https://plainframework.com).
|
51
|
+
|
plain-0.1.0/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
<!-- This file is compiled from plain/plain/README.md. Do not edit this file directly. -->
|
2
|
+
|
3
|
+
# Plain
|
4
|
+
|
5
|
+
Plain is a web framework for building products with Python.
|
6
|
+
|
7
|
+
With the core `plain` package you can build an app that:
|
8
|
+
|
9
|
+
- Matches [URL patterns](https://plainframework.com/docs/plain/plain/urls) to Python [views](https://plainframework.com/docs/plain/plain/views)
|
10
|
+
- Handles [HTTP requests and responses](https://plainframework.com/docs/plain/plain/http)
|
11
|
+
- Renders [HTML templates](https://plainframework.com/docs/plain/plain/templates) with Jinja
|
12
|
+
- Processes user input via [forms](https://plainframework.com/docs/plain/plain/forms)
|
13
|
+
- Has a [CLI interface](https://plainframework.com/docs/plain/plain/cli)
|
14
|
+
- Serves static [assets](https://plainframework.com/docs/plain/plain/assets) (CSS, JS, images)
|
15
|
+
- Can be modified with [middleware](https://plainframework.com/docs/plain/plain/middleware)
|
16
|
+
- Integrates first-party and third-party [packages](https://plainframework.com/docs/plain/plain/packages)
|
17
|
+
- Has a [preflight check system](https://plainframework.com/docs/plain/plain/preflight)
|
18
|
+
|
19
|
+
With the official Plain ecosystem packages you can:
|
20
|
+
|
21
|
+
- Integrate a full-featured [database ORM](https://plainframework.com/docs/plain-models/)
|
22
|
+
- Use a built-in [user authentication](https://plainframework.com/docs/plain-auth/) system
|
23
|
+
- [Lint and format code](https://plainframework.com/docs/plain-code/)
|
24
|
+
- Run a [database-backed cache](https://plainframework.com/docs/plain-cache/)
|
25
|
+
- [Send emails](https://plainframework.com/docs/plain-mail/)
|
26
|
+
- Streamline [local development](https://plainframework.com/docs/plain-dev/)
|
27
|
+
- Manage [feature flags](https://plainframework.com/docs/plain-flags/)
|
28
|
+
- Integrate [HTMX](https://plainframework.com/docs/plain-htmx/)
|
29
|
+
- Style with [Tailwind CSS](https://plainframework.com/docs/plain-tailwind/)
|
30
|
+
- Add [OAuth login](https://plainframework.com/docs/plain-oauth/) and API access
|
31
|
+
- Run tests with [pytest](https://plainframework.com/docs/plain-test/)
|
32
|
+
- Run a [background job worker](https://plainframework.com/docs/plain-worker/)
|
33
|
+
- Build [staff tooling and admin dashboards](https://plainframework.com/docs/plain-staff/)
|
34
|
+
|
35
|
+
Learn more at [plainframework.com](https://plainframework.com).
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Plain
|
2
|
+
|
3
|
+
Plain is a web framework for building products with Python.
|
4
|
+
|
5
|
+
With the core `plain` package you can build an app that:
|
6
|
+
|
7
|
+
- Matches [URL patterns](./urls) to Python [views](./views)
|
8
|
+
- Handles [HTTP requests and responses](./http)
|
9
|
+
- Renders [HTML templates](./templates) with Jinja
|
10
|
+
- Processes user input via [forms](./forms)
|
11
|
+
- Has a [CLI interface](./cli)
|
12
|
+
- Serves static [assets](./assets) (CSS, JS, images)
|
13
|
+
- Can be modified with [middleware](./middleware)
|
14
|
+
- Integrates first-party and third-party [packages](./packages)
|
15
|
+
- Has a [preflight check system](./preflight)
|
16
|
+
|
17
|
+
With the official Plain ecosystem packages you can:
|
18
|
+
|
19
|
+
- Integrate a full-featured [database ORM](https://plainframework.com/docs/plain-models/)
|
20
|
+
- Use a built-in [user authentication](https://plainframework.com/docs/plain-auth/) system
|
21
|
+
- [Lint and format code](https://plainframework.com/docs/plain-code/)
|
22
|
+
- Run a [database-backed cache](https://plainframework.com/docs/plain-cache/)
|
23
|
+
- [Send emails](https://plainframework.com/docs/plain-mail/)
|
24
|
+
- Streamline [local development](https://plainframework.com/docs/plain-dev/)
|
25
|
+
- Manage [feature flags](https://plainframework.com/docs/plain-flags/)
|
26
|
+
- Integrate [HTMX](https://plainframework.com/docs/plain-htmx/)
|
27
|
+
- Style with [Tailwind CSS](https://plainframework.com/docs/plain-tailwind/)
|
28
|
+
- Add [OAuth login](https://plainframework.com/docs/plain-oauth/) and API access
|
29
|
+
- Run tests with [pytest](https://plainframework.com/docs/plain-test/)
|
30
|
+
- Run a [background job worker](https://plainframework.com/docs/plain-worker/)
|
31
|
+
- Build [staff tooling and admin dashboards](https://plainframework.com/docs/plain-staff/)
|
32
|
+
|
33
|
+
Learn more at [plainframework.com](https://plainframework.com).
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Assets
|
2
|
+
|
3
|
+
Serve static assets (CSS, JS, images, etc.) for your app.
|
4
|
+
|
5
|
+
The default behavior is for Plain to serve assets directly via a middleware.
|
6
|
+
This is based on [whitenoise](http://whitenoise.evans.io/en/stable/).
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Generally speaking, the simplest way to include assests in your app is to put them either in `app/assets` or `app/<package>/assets`.
|
11
|
+
|
12
|
+
Then in your template you can use the `asset()` function to get the URL.
|
13
|
+
|
14
|
+
```html
|
15
|
+
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
|
16
|
+
```
|
17
|
+
|
18
|
+
If you ever need to reference an asset directly in Python code, you can use the `get_asset_url()` function.
|
19
|
+
|
20
|
+
```python
|
21
|
+
from plain.assets import get_asset_url
|
22
|
+
|
23
|
+
print(get_asset_url("css/style.css"))
|
24
|
+
```
|
25
|
+
|
26
|
+
## Settings
|
27
|
+
|
28
|
+
These are the default settings related to assets handling.
|
29
|
+
|
30
|
+
```python
|
31
|
+
# app/settings.py
|
32
|
+
MIDDLEWARE = [
|
33
|
+
"plain.middleware.security.SecurityMiddleware",
|
34
|
+
"plain.assets.whitenoise.middleware.WhiteNoiseMiddleware", # <--
|
35
|
+
"plain.middleware.common.CommonMiddleware",
|
36
|
+
"plain.csrf.middleware.CsrfViewMiddleware",
|
37
|
+
"plain.middleware.clickjacking.XFrameOptionsMiddleware",
|
38
|
+
]
|
39
|
+
|
40
|
+
ASSETS_BACKEND = "plain.assets.whitenoise.storage.CompressedManifestStaticFilesStorage"
|
41
|
+
|
42
|
+
# List of finder classes that know how to find assets files in
|
43
|
+
# various locations.
|
44
|
+
ASSETS_FINDERS = [
|
45
|
+
"plain.assets.finders.FileSystemFinder",
|
46
|
+
"plain.assets.finders.PackageDirectoriesFinder",
|
47
|
+
]
|
48
|
+
|
49
|
+
# Absolute path to the directory assets files should be collected to.
|
50
|
+
# Example: "/var/www/example.com/assets/"
|
51
|
+
ASSETS_ROOT = PLAIN_TEMP_PATH / "assets_collected"
|
52
|
+
|
53
|
+
# URL that handles the assets files served from ASSETS_ROOT.
|
54
|
+
# Example: "http://example.com/assets/", "http://assets.example.com/"
|
55
|
+
ASSETS_URL = "/assets/"
|
56
|
+
```
|
@@ -0,0 +1,233 @@
|
|
1
|
+
import functools
|
2
|
+
import os
|
3
|
+
|
4
|
+
from plain.assets import utils
|
5
|
+
from plain.assets.storage import FileSystemStorage
|
6
|
+
from plain.exceptions import ImproperlyConfigured
|
7
|
+
from plain.packages import packages
|
8
|
+
from plain.runtime import APP_PATH
|
9
|
+
from plain.utils._os import safe_join
|
10
|
+
from plain.utils.module_loading import import_string
|
11
|
+
|
12
|
+
# To keep track on which directories the finder has searched the static files.
|
13
|
+
searched_locations = []
|
14
|
+
|
15
|
+
|
16
|
+
APP_ASSETS_DIR = APP_PATH / "assets"
|
17
|
+
|
18
|
+
|
19
|
+
class BaseFinder:
|
20
|
+
"""
|
21
|
+
A base file finder to be used for custom assets finder classes.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def check(self, **kwargs):
|
25
|
+
raise NotImplementedError(
|
26
|
+
"subclasses may provide a check() method to verify the finder is "
|
27
|
+
"configured correctly."
|
28
|
+
)
|
29
|
+
|
30
|
+
def find(self, path, all=False):
|
31
|
+
"""
|
32
|
+
Given a relative file path, find an absolute file path.
|
33
|
+
|
34
|
+
If the ``all`` parameter is False (default) return only the first found
|
35
|
+
file path; if True, return a list of all found files paths.
|
36
|
+
"""
|
37
|
+
raise NotImplementedError(
|
38
|
+
"subclasses of BaseFinder must provide a find() method"
|
39
|
+
)
|
40
|
+
|
41
|
+
def list(self, ignore_patterns):
|
42
|
+
"""
|
43
|
+
Given an optional list of paths to ignore, return a two item iterable
|
44
|
+
consisting of the relative path and storage instance.
|
45
|
+
"""
|
46
|
+
raise NotImplementedError(
|
47
|
+
"subclasses of BaseFinder must provide a list() method"
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class FileSystemFinder(BaseFinder):
|
52
|
+
"""
|
53
|
+
A static files finder that looks in "static"
|
54
|
+
"""
|
55
|
+
|
56
|
+
def __init__(self, package_names=None, *args, **kwargs):
|
57
|
+
# List of locations with static files
|
58
|
+
self.locations = []
|
59
|
+
# Maps dir paths to an appropriate storage instance
|
60
|
+
self.storages = {}
|
61
|
+
|
62
|
+
root = APP_ASSETS_DIR
|
63
|
+
|
64
|
+
if isinstance(root, list | tuple):
|
65
|
+
prefix, root = root
|
66
|
+
else:
|
67
|
+
prefix = ""
|
68
|
+
if (prefix, root) not in self.locations:
|
69
|
+
self.locations.append((prefix, root))
|
70
|
+
for prefix, root in self.locations:
|
71
|
+
filesystem_storage = FileSystemStorage(location=root)
|
72
|
+
filesystem_storage.prefix = prefix
|
73
|
+
self.storages[root] = filesystem_storage
|
74
|
+
super().__init__(*args, **kwargs)
|
75
|
+
|
76
|
+
# def check(self, **kwargs):
|
77
|
+
# errors = []
|
78
|
+
# if settings.ASSETS_ROOT and os.path.abspath(
|
79
|
+
# settings.ASSETS_ROOT
|
80
|
+
# ) == os.path.abspath(self.path):
|
81
|
+
# errors.append(
|
82
|
+
# Error(
|
83
|
+
# "The STATICFILES_DIR setting should not contain the "
|
84
|
+
# "ASSETS_ROOT setting.",
|
85
|
+
# id="assets.E002",
|
86
|
+
# )
|
87
|
+
# )
|
88
|
+
# return errors
|
89
|
+
|
90
|
+
def find(self, path, all=False):
|
91
|
+
matches = []
|
92
|
+
for prefix, root in self.locations:
|
93
|
+
if root not in searched_locations:
|
94
|
+
searched_locations.append(root)
|
95
|
+
matched_path = self.find_location(root, path, prefix)
|
96
|
+
if matched_path:
|
97
|
+
if not all:
|
98
|
+
return matched_path
|
99
|
+
matches.append(matched_path)
|
100
|
+
return matches
|
101
|
+
|
102
|
+
def find_location(self, root, path, prefix=None):
|
103
|
+
"""
|
104
|
+
Find a requested static file in a location and return the found
|
105
|
+
absolute path (or ``None`` if no match).
|
106
|
+
"""
|
107
|
+
if prefix:
|
108
|
+
prefix = f"{prefix}{os.sep}"
|
109
|
+
if not path.startswith(prefix):
|
110
|
+
return None
|
111
|
+
path = path.removeprefix(prefix)
|
112
|
+
path = safe_join(root, path)
|
113
|
+
if os.path.exists(path):
|
114
|
+
return path
|
115
|
+
|
116
|
+
def list(self, ignore_patterns):
|
117
|
+
"""
|
118
|
+
List all files in all locations.
|
119
|
+
"""
|
120
|
+
for prefix, root in self.locations:
|
121
|
+
# Skip nonexistent directories.
|
122
|
+
if os.path.isdir(root):
|
123
|
+
storage = self.storages[root]
|
124
|
+
for path in utils.get_files(storage, ignore_patterns):
|
125
|
+
yield path, storage
|
126
|
+
|
127
|
+
|
128
|
+
class PackageDirectoriesFinder(BaseFinder):
|
129
|
+
"""
|
130
|
+
A static files finder that looks in the directory of each app as
|
131
|
+
specified in the source_dir attribute.
|
132
|
+
"""
|
133
|
+
|
134
|
+
storage_class = FileSystemStorage
|
135
|
+
source_dir = "assets"
|
136
|
+
|
137
|
+
def __init__(self, package_names=None, *args, **kwargs):
|
138
|
+
# The list of packages that are handled
|
139
|
+
self.packages = []
|
140
|
+
# Mapping of app names to storage instances
|
141
|
+
self.storages = {}
|
142
|
+
package_configs = packages.get_package_configs()
|
143
|
+
if package_names:
|
144
|
+
package_names = set(package_names)
|
145
|
+
package_configs = [ac for ac in package_configs if ac.name in package_names]
|
146
|
+
for package_config in package_configs:
|
147
|
+
app_storage = self.storage_class(
|
148
|
+
os.path.join(package_config.path, self.source_dir)
|
149
|
+
)
|
150
|
+
if os.path.isdir(app_storage.location):
|
151
|
+
self.storages[package_config.name] = app_storage
|
152
|
+
if package_config.name not in self.packages:
|
153
|
+
self.packages.append(package_config.name)
|
154
|
+
super().__init__(*args, **kwargs)
|
155
|
+
|
156
|
+
def list(self, ignore_patterns):
|
157
|
+
"""
|
158
|
+
List all files in all app storages.
|
159
|
+
"""
|
160
|
+
for storage in self.storages.values():
|
161
|
+
if storage.exists(""): # check if storage location exists
|
162
|
+
for path in utils.get_files(storage, ignore_patterns):
|
163
|
+
yield path, storage
|
164
|
+
|
165
|
+
def find(self, path, all=False):
|
166
|
+
"""
|
167
|
+
Look for files in the app directories.
|
168
|
+
"""
|
169
|
+
matches = []
|
170
|
+
for app in self.packages:
|
171
|
+
app_location = self.storages[app].location
|
172
|
+
if app_location not in searched_locations:
|
173
|
+
searched_locations.append(app_location)
|
174
|
+
match = self.find_in_app(app, path)
|
175
|
+
if match:
|
176
|
+
if not all:
|
177
|
+
return match
|
178
|
+
matches.append(match)
|
179
|
+
return matches
|
180
|
+
|
181
|
+
def find_in_app(self, app, path):
|
182
|
+
"""
|
183
|
+
Find a requested static file in an app's static locations.
|
184
|
+
"""
|
185
|
+
storage = self.storages.get(app)
|
186
|
+
# Only try to find a file if the source dir actually exists.
|
187
|
+
if storage and storage.exists(path):
|
188
|
+
matched_path = storage.path(path)
|
189
|
+
if matched_path:
|
190
|
+
return matched_path
|
191
|
+
|
192
|
+
|
193
|
+
def find(path, all=False):
|
194
|
+
"""
|
195
|
+
Find a static file with the given path using all enabled finders.
|
196
|
+
|
197
|
+
If ``all`` is ``False`` (default), return the first matching
|
198
|
+
absolute path (or ``None`` if no match). Otherwise return a list.
|
199
|
+
"""
|
200
|
+
searched_locations[:] = []
|
201
|
+
matches = []
|
202
|
+
for finder in get_finders():
|
203
|
+
result = finder.find(path, all=all)
|
204
|
+
if not all and result:
|
205
|
+
return result
|
206
|
+
if not isinstance(result, list | tuple):
|
207
|
+
result = [result]
|
208
|
+
matches.extend(result)
|
209
|
+
if matches:
|
210
|
+
return matches
|
211
|
+
# No match.
|
212
|
+
return [] if all else None
|
213
|
+
|
214
|
+
|
215
|
+
def get_finders():
|
216
|
+
from plain.runtime import settings
|
217
|
+
|
218
|
+
for finder_path in settings.ASSETS_FINDERS:
|
219
|
+
yield get_finder(finder_path)
|
220
|
+
|
221
|
+
|
222
|
+
@functools.cache
|
223
|
+
def get_finder(import_path):
|
224
|
+
"""
|
225
|
+
Import the assets finder class described by import_path, where
|
226
|
+
import_path is the full Python path to the class.
|
227
|
+
"""
|
228
|
+
Finder = import_string(import_path)
|
229
|
+
if not issubclass(Finder, BaseFinder):
|
230
|
+
raise ImproperlyConfigured(
|
231
|
+
f'Finder "{Finder}" is not a subclass of "{BaseFinder}"'
|
232
|
+
)
|
233
|
+
return Finder()
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from plain.assets.finders import get_finders
|
2
|
+
|
3
|
+
|
4
|
+
def check_finders(package_configs=None, **kwargs):
|
5
|
+
"""Check all registered assets finders."""
|
6
|
+
errors = []
|
7
|
+
for finder in get_finders():
|
8
|
+
try:
|
9
|
+
finder_errors = finder.check()
|
10
|
+
except NotImplementedError:
|
11
|
+
pass
|
12
|
+
else:
|
13
|
+
errors.extend(finder_errors)
|
14
|
+
return errors
|