plain 0.52.1__py3-none-any.whl → 0.53.0__py3-none-any.whl
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/CHANGELOG.md +23 -0
- plain/assets/README.md +6 -4
- plain/cli/build.py +2 -5
- plain/cli/docs.py +3 -7
- plain/cli/preflight.py +1 -1
- plain/cli/urls.py +1 -4
- plain/http/cookie.py +44 -0
- plain/http/request.py +15 -0
- plain/http/response.py +5 -12
- plain/templates/jinja/filters.py +20 -0
- {plain-0.52.1.dist-info → plain-0.53.0.dist-info}/METADATA +1 -1
- {plain-0.52.1.dist-info → plain-0.53.0.dist-info}/RECORD +15 -15
- {plain-0.52.1.dist-info → plain-0.53.0.dist-info}/WHEEL +0 -0
- {plain-0.52.1.dist-info → plain-0.53.0.dist-info}/entry_points.txt +0 -0
- {plain-0.52.1.dist-info → plain-0.53.0.dist-info}/licenses/LICENSE +0 -0
plain/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# plain changelog
|
2
2
|
|
3
|
+
## [0.53.0](https://github.com/dropseed/plain/releases/plain@0.53.0) (2025-07-18)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Added a `pluralize` filter for Jinja templates to handle singular/plural forms ([4cef9829ed](https://github.com/dropseed/plain/commit/4cef9829ed))
|
8
|
+
- Added `get_signed_cookie()` method to `HttpRequest` for retrieving and verifying signed cookies ([f8796c8786](https://github.com/dropseed/plain/commit/f8796c8786))
|
9
|
+
- Improved CLI error handling by using `click.UsageError` instead of manual error printing ([88f06c5184](https://github.com/dropseed/plain/commit/88f06c5184))
|
10
|
+
- Simplified preflight check success message ([adffc06152](https://github.com/dropseed/plain/commit/adffc06152))
|
11
|
+
|
12
|
+
### Upgrade instructions
|
13
|
+
|
14
|
+
- No changes required
|
15
|
+
|
16
|
+
## [0.52.2](https://github.com/dropseed/plain/releases/plain@0.52.2) (2025-06-27)
|
17
|
+
|
18
|
+
### What's changed
|
19
|
+
|
20
|
+
- Improved documentation for the assets subsystem: the `AssetsRouter` reference in the Assets README now links directly to the source code for quicker navigation ([65437e9](https://github.com/dropseed/plain/commit/65437e9bb1a522c7ababe0fc195f63bc5fd6c4d4))
|
21
|
+
|
22
|
+
### Upgrade instructions
|
23
|
+
|
24
|
+
- No changes required
|
25
|
+
|
3
26
|
## [0.52.1](https://github.com/dropseed/plain/releases/plain@0.52.1) (2025-06-27)
|
4
27
|
|
5
28
|
### What's changed
|
plain/assets/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
To serve assets, put them in `app/assets` or `app/{package}/assets`.
|
8
8
|
|
9
|
-
Then include the `AssetsRouter` in your own router, typically under the `assets/` path.
|
9
|
+
Then include the [`AssetsRouter`](./urls.py#AssetsRouter) in your own router, typically under the `assets/` path.
|
10
10
|
|
11
11
|
```python
|
12
12
|
# app/urls.py
|
@@ -40,7 +40,7 @@ In production, one of your deployment steps should be to compile the assets.
|
|
40
40
|
plain build
|
41
41
|
```
|
42
42
|
|
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`.
|
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`.
|
44
44
|
|
45
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.
|
46
46
|
|
@@ -64,6 +64,8 @@ class AppRouter(Router):
|
|
64
64
|
|
65
65
|
### How do you reference assets in Python code?
|
66
66
|
|
67
|
+
There is a [`get_asset_url`](./urls.py#get_asset_url) function that you can use to get the URL of an asset in Python code. This is useful if you need to reference an asset in a non-template context, such as in a redirect or an API response.
|
68
|
+
|
67
69
|
```python
|
68
70
|
from plain.assets.urls import get_asset_url
|
69
71
|
|
@@ -94,7 +96,7 @@ ls .plain/assets/compiled
|
|
94
96
|
./example-upload-to-cdn-script
|
95
97
|
```
|
96
98
|
|
97
|
-
Use the `ASSETS_BASE_URL` setting to tell the `{{ asset() }}` template function where to point.
|
99
|
+
Use the [`ASSETS_BASE_URL`](../runtime/global_settings.py#ASSETS_BASE_URL) setting to tell the `{{ asset() }}` template function where to point.
|
98
100
|
|
99
101
|
```python
|
100
102
|
# app/settings.py
|
@@ -107,4 +109,4 @@ The default behavior is to fingerprint assets, which is an exact copy of the ori
|
|
107
109
|
|
108
110
|
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.
|
109
111
|
|
110
|
-
Note that by default, the `ASSETS_REDIRECT_ORIGINAL` setting is `True`, which will redirect requests for the original file to the fingerprinted file.
|
112
|
+
Note that by default, the [`ASSETS_REDIRECT_ORIGINAL`](../runtime/global_settings.py#ASSETS_REDIRECT_ORIGINAL) setting is `True`, which will redirect requests for the original file to the fingerprinted file.
|
plain/cli/build.py
CHANGED
@@ -37,12 +37,9 @@ def build(keep_original, fingerprint, compress):
|
|
37
37
|
"""Pre-deployment build step (compile assets, css, js, etc.)"""
|
38
38
|
|
39
39
|
if not keep_original and not fingerprint:
|
40
|
-
click.
|
41
|
-
"You must either keep the original assets or fingerprint them."
|
42
|
-
fg="red",
|
43
|
-
err=True,
|
40
|
+
raise click.UsageError(
|
41
|
+
"You must either keep the original assets or fingerprint them."
|
44
42
|
)
|
45
|
-
sys.exit(1)
|
46
43
|
|
47
44
|
# Run user-defined build commands first
|
48
45
|
pyproject_path = plain.runtime.APP_PATH.parent / "pyproject.toml"
|
plain/cli/docs.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import ast
|
2
2
|
import importlib.util
|
3
|
-
import sys
|
4
3
|
from pathlib import Path
|
5
4
|
|
6
5
|
import click
|
@@ -16,8 +15,7 @@ from .output import iterate_markdown
|
|
16
15
|
@click.argument("module", default="")
|
17
16
|
def docs(module, llm, open):
|
18
17
|
if not module and not llm:
|
19
|
-
click.
|
20
|
-
sys.exit(1)
|
18
|
+
raise click.UsageError("You must specify a module or use --llm")
|
21
19
|
|
22
20
|
if llm:
|
23
21
|
paths = [Path(__file__).parent.parent]
|
@@ -49,14 +47,12 @@ def docs(module, llm, open):
|
|
49
47
|
# Get the README.md file for the module
|
50
48
|
spec = importlib.util.find_spec(module)
|
51
49
|
if not spec:
|
52
|
-
click.
|
53
|
-
sys.exit(1)
|
50
|
+
raise click.UsageError(f"Module {module} not found")
|
54
51
|
|
55
52
|
module_path = Path(spec.origin).parent
|
56
53
|
readme_path = module_path / "README.md"
|
57
54
|
if not readme_path.exists():
|
58
|
-
click.
|
59
|
-
sys.exit(1)
|
55
|
+
raise click.UsageError(f"README.md not found for {module}")
|
60
56
|
|
61
57
|
if open:
|
62
58
|
click.launch(str(readme_path))
|
plain/cli/preflight.py
CHANGED
@@ -123,4 +123,4 @@ def preflight_checks(package_label, deploy, fail_level, database):
|
|
123
123
|
msg = header + body + footer
|
124
124
|
click.echo(msg, err=True)
|
125
125
|
else:
|
126
|
-
click.secho("✔
|
126
|
+
click.secho("✔ Checks passed", err=True, fg="green")
|
plain/cli/urls.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
1
|
import click
|
4
2
|
|
5
3
|
|
@@ -17,8 +15,7 @@ def list_urls(flat):
|
|
17
15
|
from plain.urls import URLResolver, get_resolver
|
18
16
|
|
19
17
|
if not settings.URLS_ROUTER:
|
20
|
-
click.
|
21
|
-
sys.exit(1)
|
18
|
+
raise click.UsageError("URLS_ROUTER is not set")
|
22
19
|
|
23
20
|
resolver = get_resolver(settings.URLS_ROUTER)
|
24
21
|
if flat:
|
plain/http/cookie.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
from http import cookies
|
2
2
|
|
3
|
+
from plain.runtime import settings
|
4
|
+
from plain.signing import BadSignature, TimestampSigner
|
5
|
+
from plain.utils.encoding import force_bytes
|
6
|
+
|
3
7
|
|
4
8
|
def parse_cookie(cookie):
|
5
9
|
"""
|
@@ -18,3 +22,43 @@ def parse_cookie(cookie):
|
|
18
22
|
# unquote using Python's algorithm.
|
19
23
|
cookiedict[key] = cookies._unquote(val)
|
20
24
|
return cookiedict
|
25
|
+
|
26
|
+
|
27
|
+
def _cookie_key(key):
|
28
|
+
"""
|
29
|
+
Generate a key for cookie signing that matches the pattern used by
|
30
|
+
set_signed_cookie and get_signed_cookie.
|
31
|
+
"""
|
32
|
+
return b"plain.http.cookies" + force_bytes(key)
|
33
|
+
|
34
|
+
|
35
|
+
def get_signed_cookie_signer(key, salt=""):
|
36
|
+
"""
|
37
|
+
Create a TimestampSigner for signed cookies with the same configuration
|
38
|
+
used by both set_signed_cookie and get_signed_cookie.
|
39
|
+
"""
|
40
|
+
return TimestampSigner(
|
41
|
+
key=_cookie_key(settings.SECRET_KEY),
|
42
|
+
fallback_keys=map(_cookie_key, settings.SECRET_KEY_FALLBACKS),
|
43
|
+
salt=key + salt,
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
def sign_cookie_value(key, value, salt=""):
|
48
|
+
"""
|
49
|
+
Sign a cookie value using the standard Plain cookie signing approach.
|
50
|
+
"""
|
51
|
+
signer = get_signed_cookie_signer(key, salt)
|
52
|
+
return signer.sign(value)
|
53
|
+
|
54
|
+
|
55
|
+
def unsign_cookie_value(key, signed_value, salt="", max_age=None, default=None):
|
56
|
+
"""
|
57
|
+
Unsign a cookie value using the standard Plain cookie signing approach.
|
58
|
+
Returns the default value if the signature is invalid or the cookie has expired.
|
59
|
+
"""
|
60
|
+
signer = get_signed_cookie_signer(key, salt)
|
61
|
+
try:
|
62
|
+
return signer.unsign(signed_value, max_age=max_age)
|
63
|
+
except BadSignature:
|
64
|
+
return default
|
plain/http/request.py
CHANGED
@@ -13,6 +13,7 @@ from plain.exceptions import (
|
|
13
13
|
RequestDataTooBig,
|
14
14
|
TooManyFieldsSent,
|
15
15
|
)
|
16
|
+
from plain.http.cookie import unsign_cookie_value
|
16
17
|
from plain.http.multipartparser import (
|
17
18
|
MultiPartParser,
|
18
19
|
MultiPartParserError,
|
@@ -427,6 +428,20 @@ class HttpRequest:
|
|
427
428
|
def readlines(self):
|
428
429
|
return list(self)
|
429
430
|
|
431
|
+
def get_signed_cookie(self, key, default=None, salt="", max_age=None):
|
432
|
+
"""
|
433
|
+
Retrieve a cookie value signed with the SECRET_KEY.
|
434
|
+
|
435
|
+
Return default if the cookie doesn't exist or signature verification fails.
|
436
|
+
"""
|
437
|
+
|
438
|
+
try:
|
439
|
+
cookie_value = self.cookies[key]
|
440
|
+
except KeyError:
|
441
|
+
return default
|
442
|
+
|
443
|
+
return unsign_cookie_value(key, cookie_value, salt, max_age, default)
|
444
|
+
|
430
445
|
|
431
446
|
class HttpHeaders(CaseInsensitiveMapping):
|
432
447
|
HTTP_PREFIX = "HTTP_"
|
plain/http/response.py
CHANGED
@@ -12,13 +12,14 @@ from http.client import responses
|
|
12
12
|
from http.cookies import SimpleCookie
|
13
13
|
from urllib.parse import urlparse
|
14
14
|
|
15
|
-
from plain import signals
|
15
|
+
from plain import signals
|
16
16
|
from plain.exceptions import DisallowedRedirect
|
17
|
+
from plain.http.cookie import sign_cookie_value
|
17
18
|
from plain.json import PlainJSONEncoder
|
18
19
|
from plain.runtime import settings
|
19
20
|
from plain.utils import timezone
|
20
21
|
from plain.utils.datastructures import CaseInsensitiveMapping
|
21
|
-
from plain.utils.encoding import
|
22
|
+
from plain.utils.encoding import iri_to_uri
|
22
23
|
from plain.utils.http import content_disposition_header, http_date
|
23
24
|
from plain.utils.regex_helper import _lazy_re_compile
|
24
25
|
|
@@ -260,16 +261,8 @@ class ResponseBase:
|
|
260
261
|
def set_signed_cookie(self, key, value, salt="", **kwargs):
|
261
262
|
"""Set a cookie signed with the SECRET_KEY."""
|
262
263
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
signer = signing.TimestampSigner(
|
267
|
-
key=_cookie_key(settings.SECRET_KEY),
|
268
|
-
fallback_keys=map(_cookie_key, settings.SECRET_KEY_FALLBACKS),
|
269
|
-
salt=key + salt,
|
270
|
-
)
|
271
|
-
value = signer.sign(value)
|
272
|
-
return self.set_cookie(key, value, **kwargs)
|
264
|
+
signed_value = sign_cookie_value(key, value, salt)
|
265
|
+
return self.set_cookie(key, signed_value, **kwargs)
|
273
266
|
|
274
267
|
def delete_cookie(self, key, path="/", domain=None, samesite=None):
|
275
268
|
# Browsers can ignore the Set-Cookie header if the cookie doesn't use
|
plain/templates/jinja/filters.py
CHANGED
@@ -15,6 +15,25 @@ def localtime_filter(value, timezone=None):
|
|
15
15
|
return localtime(value, timezone)
|
16
16
|
|
17
17
|
|
18
|
+
def pluralize_filter(value, singular="", plural="s"):
|
19
|
+
"""Returns plural suffix based on the value count.
|
20
|
+
|
21
|
+
Usage:
|
22
|
+
{{ count }} item{{ count|pluralize }}
|
23
|
+
{{ count }} ox{{ count|pluralize("en") }}
|
24
|
+
{{ count }} cact{{ count|pluralize("us","i") }}
|
25
|
+
"""
|
26
|
+
try:
|
27
|
+
count = int(value)
|
28
|
+
except (ValueError, TypeError):
|
29
|
+
return singular
|
30
|
+
|
31
|
+
if count == 1:
|
32
|
+
return singular
|
33
|
+
|
34
|
+
return plural
|
35
|
+
|
36
|
+
|
18
37
|
default_filters = {
|
19
38
|
# The standard Python ones
|
20
39
|
"strftime": datetime.datetime.strftime,
|
@@ -27,4 +46,5 @@ default_filters = {
|
|
27
46
|
"timesince": timesince,
|
28
47
|
"json_script": json_script,
|
29
48
|
"islice": islice, # slice for dict.items()
|
49
|
+
"pluralize": pluralize_filter,
|
30
50
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
plain/CHANGELOG.md,sha256=
|
1
|
+
plain/CHANGELOG.md,sha256=DHF0llr5trZE_8C0138MnuFQEAM9W2jM9nL0xfjjz8Y,4472
|
2
2
|
plain/README.md,sha256=gik6DBZcJAITcm4WRq_L53AxkjY45eQLafyTCSf0CKE,3986
|
3
3
|
plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
|
4
4
|
plain/debug.py,sha256=XdjnXcbPGsi0J2SpHGaLthhYU5AjhBlkHdemaP4sbYY,758
|
@@ -8,7 +8,7 @@ plain/paginator.py,sha256=iXiOyt2r_YwNrkqCRlaU7V-M_BKaaQ8XZElUBVa6yeU,5844
|
|
8
8
|
plain/signing.py,sha256=r2KvCOxkrSWCULFxYa9BHYx3L3a2oLq8RDnq_92inTw,8207
|
9
9
|
plain/validators.py,sha256=TePzFHzwR4JXUAZ_Y2vC6mkKgVxHX3QBXI6Oex0rV8c,19236
|
10
10
|
plain/wsgi.py,sha256=R6k5FiAElvGDApEbMPTT0MPqSD7n2e2Az5chQqJZU0I,236
|
11
|
-
plain/assets/README.md,sha256=
|
11
|
+
plain/assets/README.md,sha256=8OW6y4rs0PqGvcbrndPJjOndcDUQUAgnO-Hj_PDHpWI,4151
|
12
12
|
plain/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
plain/assets/compile.py,sha256=DT85ygnNcY2_7VeozdsQlBvA-DzV7enUIMO_igJ1PjY,3296
|
14
14
|
plain/assets/finders.py,sha256=2k8QZAbfUbc1LykxbzdazTSB6xNxJZnsZaGhWbSFZZs,1452
|
@@ -20,22 +20,22 @@ plain/chores/__init__.py,sha256=r9TXtQCH-VbvfnIJ5F8FxgQC35GRWFOfmMZN3q9niLg,67
|
|
20
20
|
plain/chores/registry.py,sha256=V3WjuekRI22LFvJbqSkUXQtiOtuE2ZK8gKV1TRvxRUI,1866
|
21
21
|
plain/cli/README.md,sha256=GzBry6mEilhM80SfVUg02ydGwAk0m-s6FAqQR1nRsMM,2022
|
22
22
|
plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
|
23
|
-
plain/cli/build.py,sha256=
|
23
|
+
plain/cli/build.py,sha256=Lo6AYghJz0DM9fIVUSiBSOKa5vR0XCOxZWEjza6sc8Q,3172
|
24
24
|
plain/cli/changelog.py,sha256=j-k1yZk9mpm-fLZgeWastiyIisxNSuAJfXTQ2B6WQmk,3457
|
25
25
|
plain/cli/chores.py,sha256=xXSSFvr8T5jWfLWqe6E8YVMw1BkQxyOHHVuY0x9RH0A,2412
|
26
26
|
plain/cli/core.py,sha256=D3t83ujjjHayblM-RuttrGoNf8hMV9-l3zQsbhVAjWU,2991
|
27
|
-
plain/cli/docs.py,sha256=
|
27
|
+
plain/cli/docs.py,sha256=5-2_nQnInZAzHu3VnMW88gZyrhukhdjrkMKTMt0RRpI,7367
|
28
28
|
plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
|
29
29
|
plain/cli/help.py,sha256=NefZSEIixrX_WELVSnJDHRpLDWf7_4PXmkkMm3Q2mzo,787
|
30
30
|
plain/cli/output.py,sha256=Fe3xS6Va4Bi1ZNrqi0nh09THTsdCyMW2b9SPY5I4n-o,1318
|
31
|
-
plain/cli/preflight.py,sha256=
|
31
|
+
plain/cli/preflight.py,sha256=8tHBD4L4nPLUKThfaYx3SUZSJzC48oV2m_Hbn6W4ODc,4124
|
32
32
|
plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
|
33
33
|
plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
|
34
34
|
plain/cli/scaffold.py,sha256=mcywA9DzfwoBSqWl5-Zpgcy1mTNUGEgdvoxXUrGcEVk,1351
|
35
35
|
plain/cli/settings.py,sha256=9cx4bue664I2P7kUedlf4YhCPB0tSKSE4Q8mGyzEv2o,1995
|
36
36
|
plain/cli/shell.py,sha256=iIwvlTdTBjLBBUdXMAmIRWSoynszOZI79-mrBg4RegU,1373
|
37
37
|
plain/cli/startup.py,sha256=wLaFuyUb4ewWhtehBCGicrRCXIIGCRbeCT3ce9hUv-A,1022
|
38
|
-
plain/cli/urls.py,sha256=
|
38
|
+
plain/cli/urls.py,sha256=ghCW36aRszxmTo06A50FIvYopb6kQ07QekkDzM6_A1o,3824
|
39
39
|
plain/cli/utils.py,sha256=VwlIh0z7XxzVV8I3qM2kZo07fkJFPoeeVZa1ODG616k,258
|
40
40
|
plain/csrf/README.md,sha256=nxCpPk1HF5eAM-7paxg9D-9RVCU9jXsSPAVHkJvA_DU,717
|
41
41
|
plain/csrf/middleware.py,sha256=U3B9R7ciO_UAf7O3jdNtVu6QZ_3Yrm8isRdnW6bVKX4,17401
|
@@ -48,10 +48,10 @@ plain/forms/fields.py,sha256=OyL4eZIgJ_XMLPHGar17hLepFmwHV-hSnb_n7s18yUU,34709
|
|
48
48
|
plain/forms/forms.py,sha256=hF7Dl8rEaiBTZhFQyfbh1Zf54BSEka8RYpBiGqkTa8I,10441
|
49
49
|
plain/http/README.md,sha256=F9wbahgSU3jIDEG14gJjdPJRem4weUNvwnwhb7o3cu0,722
|
50
50
|
plain/http/__init__.py,sha256=DIsDRbBsCGa4qZgq-fUuQS0kkxfbTU_3KpIM9VvH04w,1067
|
51
|
-
plain/http/cookie.py,sha256=
|
51
|
+
plain/http/cookie.py,sha256=THd7nOl-2ugeBPKgOhbD87aM2oxUbNH8HWrarUn0fpM,1955
|
52
52
|
plain/http/multipartparser.py,sha256=Z1dFJNAd8N5RHUuF67jh1jBfZOFepORsre_3ee6CgOQ,27266
|
53
|
-
plain/http/request.py,sha256=
|
54
|
-
plain/http/response.py,sha256=
|
53
|
+
plain/http/request.py,sha256=93b2gqkfEsBczUyP_9vlueVoxyzzfbnJ423PDAk8aHc,26103
|
54
|
+
plain/http/response.py,sha256=0xUhkTiT6JwohdwA7ymY2vpdCQVl4hnEExjk01LrJbg,23734
|
55
55
|
plain/internal/__init__.py,sha256=fVBaYLCXEQc-7riHMSlw3vMTTuF7-0Bj2I8aGzv0o0w,171
|
56
56
|
plain/internal/files/__init__.py,sha256=VctFgox4Q1AWF3klPaoCC5GIw5KeLafYjY5JmN8mAVw,63
|
57
57
|
plain/internal/files/base.py,sha256=2z19tik2_xgXlI6nfVZ4woSF9WB0RSUzsvOfi1Bz8Wg,4113
|
@@ -100,7 +100,7 @@ plain/templates/core.py,sha256=iw58EAmyyv8N5HDA-Sq4-fLgz_qx8v8WJfurgR116jw,625
|
|
100
100
|
plain/templates/jinja/__init__.py,sha256=xvYif0feMYR9pWjN0czthq2dk3qI4D5UQjgj9yp4dNA,2776
|
101
101
|
plain/templates/jinja/environments.py,sha256=9plifzvQj--aTN1cCpJ2WdzQxZJpzB8S_4hghgQRQT0,2064
|
102
102
|
plain/templates/jinja/extensions.py,sha256=AEmmmHDbdRW8fhjYDzq9eSSNbp9WHsXenD8tPthjc0s,1351
|
103
|
-
plain/templates/jinja/filters.py,sha256=
|
103
|
+
plain/templates/jinja/filters.py,sha256=ft5XUr4OLeQayn-MSxrycpFLyyN_yEo7j5WhWMwpTOs,1445
|
104
104
|
plain/templates/jinja/globals.py,sha256=VMpuMZvwWOmb5MbzKK-ox-QEX_WSsXFxq0mm8biJgaU,558
|
105
105
|
plain/test/README.md,sha256=fv4YzziU2QxgcNHSgv7aDUO45sDOofVuCNrV1NPbWzo,1106
|
106
106
|
plain/test/__init__.py,sha256=MhNHtp7MYBl9kq-pMRGY11kJ6kU1I6vOkjNkit1TYRg,94
|
@@ -149,8 +149,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
|
|
149
149
|
plain/views/objects.py,sha256=GGbcfg_9fPZ-PiaBwIHG2e__8GfWDR7JQtQ15wTyiHg,5970
|
150
150
|
plain/views/redirect.py,sha256=daq2cQIkdDF78bt43sjuZxRAyJm_t_SKw6tyPmiXPIc,1985
|
151
151
|
plain/views/templates.py,sha256=ivkI7LU7BXDQ0d4Geab96Is4-Cp03KbIntXRT1J8e6I,2139
|
152
|
-
plain-0.
|
153
|
-
plain-0.
|
154
|
-
plain-0.
|
155
|
-
plain-0.
|
156
|
-
plain-0.
|
152
|
+
plain-0.53.0.dist-info/METADATA,sha256=n3gNCFOwyioHwL_hKBiAmIGxSQG6CBM-XjM-x5EMq5I,4297
|
153
|
+
plain-0.53.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
154
|
+
plain-0.53.0.dist-info/entry_points.txt,sha256=nn4uKTRRZuEKOJv3810s3jtSMW0Gew7XDYiKIvBRR6M,93
|
155
|
+
plain-0.53.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
156
|
+
plain-0.53.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|