plain 0.72.0__py3-none-any.whl → 0.72.2__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 +21 -0
- plain/internal/handlers/base.py +3 -3
- plain/internal/handlers/wsgi.py +3 -1
- plain/packages/registry.py +0 -4
- plain/test/client.py +111 -51
- plain/views/README.md +5 -5
- {plain-0.72.0.dist-info → plain-0.72.2.dist-info}/METADATA +1 -1
- {plain-0.72.0.dist-info → plain-0.72.2.dist-info}/RECORD +11 -11
- {plain-0.72.0.dist-info → plain-0.72.2.dist-info}/WHEEL +0 -0
- {plain-0.72.0.dist-info → plain-0.72.2.dist-info}/entry_points.txt +0 -0
- {plain-0.72.0.dist-info → plain-0.72.2.dist-info}/licenses/LICENSE +0 -0
plain/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
# plain changelog
|
2
2
|
|
3
|
+
## [0.72.2](https://github.com/dropseed/plain/releases/plain@0.72.2) (2025-10-06)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Improved type annotations for test client responses with new `ClientResponse` wrapper class ([369353f9d6](https://github.com/dropseed/plain/commit/369353f9d6))
|
8
|
+
- Enhanced internal type checking for WSGI handler and request/response types ([50463b00c3](https://github.com/dropseed/plain/commit/50463b00c3))
|
9
|
+
|
10
|
+
### Upgrade instructions
|
11
|
+
|
12
|
+
- No changes required
|
13
|
+
|
14
|
+
## [0.72.1](https://github.com/dropseed/plain/releases/plain@0.72.1) (2025-10-02)
|
15
|
+
|
16
|
+
### What's changed
|
17
|
+
|
18
|
+
- Fixed documentation examples to use the correct view attribute names (`self.user` instead of `self.request.user`) ([f6278d9](https://github.com/dropseed/plain/commit/f6278d9bb4))
|
19
|
+
|
20
|
+
### Upgrade instructions
|
21
|
+
|
22
|
+
- No changes required
|
23
|
+
|
3
24
|
## [0.72.0](https://github.com/dropseed/plain/releases/plain@0.72.0) (2025-10-02)
|
4
25
|
|
5
26
|
### What's changed
|
plain/internal/handlers/base.py
CHANGED
@@ -18,7 +18,7 @@ from .exception import convert_exception_to_response
|
|
18
18
|
if TYPE_CHECKING:
|
19
19
|
from collections.abc import Callable
|
20
20
|
|
21
|
-
from plain.http import Request, Response
|
21
|
+
from plain.http import Request, Response, ResponseBase
|
22
22
|
from plain.urls import ResolverMatch
|
23
23
|
|
24
24
|
logger = logging.getLogger("plain.request")
|
@@ -72,7 +72,7 @@ class BaseHandler:
|
|
72
72
|
# as a flag for initialization being complete.
|
73
73
|
self._middleware_chain = handler
|
74
74
|
|
75
|
-
def get_response(self, request: Request) ->
|
75
|
+
def get_response(self, request: Request) -> ResponseBase:
|
76
76
|
"""Return a Response object for the given Request."""
|
77
77
|
|
78
78
|
span_attributes = {
|
@@ -124,7 +124,7 @@ class BaseHandler:
|
|
124
124
|
)
|
125
125
|
return response
|
126
126
|
|
127
|
-
def _get_response(self, request: Request) ->
|
127
|
+
def _get_response(self, request: Request) -> ResponseBase:
|
128
128
|
"""
|
129
129
|
Resolve and call the view, then apply view, exception, and
|
130
130
|
template_response middleware. This method is everything that happens
|
plain/internal/handlers/wsgi.py
CHANGED
@@ -16,6 +16,8 @@ if TYPE_CHECKING:
|
|
16
16
|
from collections.abc import Callable, Iterable
|
17
17
|
from typing import Any
|
18
18
|
|
19
|
+
from plain.http import ResponseBase
|
20
|
+
|
19
21
|
_slashes_re = _lazy_re_compile(rb"/+")
|
20
22
|
|
21
23
|
|
@@ -141,7 +143,7 @@ class WSGIHandler(base.BaseHandler):
|
|
141
143
|
self,
|
142
144
|
environ: dict[str, Any],
|
143
145
|
start_response: Callable[[str, list[tuple[str, str]]], Any],
|
144
|
-
) -> Iterable[bytes]:
|
146
|
+
) -> ResponseBase | Iterable[bytes]:
|
145
147
|
signals.request_started.send(sender=self.__class__, environ=environ)
|
146
148
|
request = WSGIRequest(environ)
|
147
149
|
response = self.get_response(request)
|
plain/packages/registry.py
CHANGED
@@ -6,15 +6,11 @@ from collections import Counter
|
|
6
6
|
from collections.abc import Iterable
|
7
7
|
from importlib import import_module
|
8
8
|
from importlib.util import find_spec
|
9
|
-
from typing import TYPE_CHECKING
|
10
9
|
|
11
10
|
from plain.exceptions import ImproperlyConfigured, PackageRegistryNotReady
|
12
11
|
|
13
12
|
from .config import PackageConfig
|
14
13
|
|
15
|
-
if TYPE_CHECKING:
|
16
|
-
pass
|
17
|
-
|
18
14
|
CONFIG_MODULE_NAME = "config"
|
19
15
|
|
20
16
|
|
plain/test/client.py
CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
import sys
|
5
|
-
from functools import partial
|
6
5
|
from http import HTTPStatus
|
7
6
|
from http.cookies import SimpleCookie
|
8
7
|
from io import BytesIO, IOBase
|
@@ -26,10 +25,12 @@ from .encoding import encode_multipart
|
|
26
25
|
from .exceptions import RedirectCycleError
|
27
26
|
|
28
27
|
if TYPE_CHECKING:
|
29
|
-
from plain.http import Response
|
28
|
+
from plain.http import Response, ResponseBase
|
29
|
+
from plain.urls import ResolverMatch
|
30
30
|
|
31
31
|
__all__ = (
|
32
32
|
"Client",
|
33
|
+
"ClientResponse",
|
33
34
|
"RequestFactory",
|
34
35
|
)
|
35
36
|
|
@@ -41,6 +42,79 @@ _CONTENT_TYPE_RE = _lazy_re_compile(r".*; charset=([\w-]+);?")
|
|
41
42
|
_JSON_CONTENT_TYPE_RE = _lazy_re_compile(r"^application\/(.+\+)?json")
|
42
43
|
|
43
44
|
|
45
|
+
class ClientResponse:
|
46
|
+
"""
|
47
|
+
Response wrapper returned by test Client with test-specific attributes.
|
48
|
+
|
49
|
+
Wraps any ResponseBase subclass and adds attributes useful for testing,
|
50
|
+
while delegating all other attribute access to the wrapped response.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
response: ResponseBase,
|
56
|
+
client: Client,
|
57
|
+
request: dict[str, Any],
|
58
|
+
exc_info: tuple[Any, Any, Any] | None,
|
59
|
+
):
|
60
|
+
# Store wrapped response in __dict__ directly to avoid __setattr__ recursion
|
61
|
+
object.__setattr__(self, "_response", response)
|
62
|
+
object.__setattr__(self, "_json_cache", None)
|
63
|
+
# Test-specific attributes
|
64
|
+
self.client = client
|
65
|
+
self.request = request
|
66
|
+
self.wsgi_request: WSGIRequest
|
67
|
+
self.redirect_chain: list[tuple[str, int]]
|
68
|
+
self.resolver_match: SimpleLazyObject | ResolverMatch
|
69
|
+
self.exc_info = exc_info
|
70
|
+
# Optional: set by plain.auth if available
|
71
|
+
# self.user: Model
|
72
|
+
|
73
|
+
def json(self, **extra: Any) -> Any:
|
74
|
+
"""Parse response content as JSON."""
|
75
|
+
_json_cache = object.__getattribute__(self, "_json_cache")
|
76
|
+
if _json_cache is None:
|
77
|
+
response = object.__getattribute__(self, "_response")
|
78
|
+
content_type = response.headers.get("Content-Type", "")
|
79
|
+
if not _JSON_CONTENT_TYPE_RE.match(content_type):
|
80
|
+
raise ValueError(
|
81
|
+
f'Content-Type header is "{content_type}", not "application/json"'
|
82
|
+
)
|
83
|
+
_json_cache = json.loads(
|
84
|
+
response.content.decode(response.charset),
|
85
|
+
**extra,
|
86
|
+
)
|
87
|
+
object.__setattr__(self, "_json_cache", _json_cache)
|
88
|
+
return _json_cache
|
89
|
+
|
90
|
+
@property
|
91
|
+
def url(self) -> str:
|
92
|
+
"""
|
93
|
+
Return redirect URL if this is a redirect response.
|
94
|
+
|
95
|
+
This property exists on ResponseRedirect and is added for redirects.
|
96
|
+
"""
|
97
|
+
response = object.__getattribute__(self, "_response")
|
98
|
+
if hasattr(response, "url"):
|
99
|
+
return response.url # type: ignore[attr-defined,return-value]
|
100
|
+
# For non-redirect responses, try to get Location header
|
101
|
+
if "Location" in response.headers:
|
102
|
+
return response.headers["Location"]
|
103
|
+
raise AttributeError(f"{response.__class__.__name__} has no attribute 'url'")
|
104
|
+
|
105
|
+
def __getattr__(self, name: str) -> Any:
|
106
|
+
"""Delegate attribute access to the wrapped response."""
|
107
|
+
return getattr(object.__getattribute__(self, "_response"), name)
|
108
|
+
|
109
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
110
|
+
"""Set attributes on the wrapper itself."""
|
111
|
+
object.__setattr__(self, name, value)
|
112
|
+
|
113
|
+
def __repr__(self) -> str:
|
114
|
+
"""Return repr of wrapped response."""
|
115
|
+
return repr(object.__getattribute__(self, "_response"))
|
116
|
+
|
117
|
+
|
44
118
|
@internalcode
|
45
119
|
class FakePayload(IOBase):
|
46
120
|
"""
|
@@ -470,7 +544,7 @@ class Client:
|
|
470
544
|
"""Set the cookies on the request factory."""
|
471
545
|
self._request_factory.cookies = value
|
472
546
|
|
473
|
-
def request(self, **request: Any) ->
|
547
|
+
def request(self, **request: Any) -> ClientResponse:
|
474
548
|
"""
|
475
549
|
Make a generic request. Compose the environment dictionary and pass
|
476
550
|
to the handler, return the result of the handler. Assume defaults for
|
@@ -487,31 +561,36 @@ class Client:
|
|
487
561
|
finally:
|
488
562
|
# signals.template_rendered.disconnect(dispatch_uid=signal_uid)
|
489
563
|
got_request_exception.disconnect(dispatch_uid=exception_uid)
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
564
|
+
|
565
|
+
# Wrap the response in ClientResponse for test-specific attributes
|
566
|
+
client_response = ClientResponse(
|
567
|
+
response=response,
|
568
|
+
client=self,
|
569
|
+
request=request,
|
570
|
+
exc_info=self.exc_info,
|
571
|
+
)
|
572
|
+
|
573
|
+
# Check for signaled exceptions and potentially re-raise
|
574
|
+
self.check_exception()
|
496
575
|
|
497
576
|
# If the request had a user, make it available on the response.
|
498
577
|
try:
|
499
578
|
from plain.auth.requests import get_request_user
|
500
579
|
|
501
|
-
|
580
|
+
client_response.user = get_request_user(client_response.wsgi_request)
|
502
581
|
except ImportError:
|
503
582
|
pass
|
504
583
|
|
505
584
|
# Attach the ResolverMatch instance to the response.
|
506
585
|
resolver = get_resolver()
|
507
|
-
|
586
|
+
client_response.resolver_match = SimpleLazyObject(
|
508
587
|
lambda: resolver.resolve(request["PATH_INFO"]),
|
509
588
|
)
|
510
589
|
|
511
590
|
# Update persistent cookie data.
|
512
|
-
if
|
513
|
-
self.cookies.update(
|
514
|
-
return
|
591
|
+
if client_response.cookies:
|
592
|
+
self.cookies.update(client_response.cookies)
|
593
|
+
return client_response
|
515
594
|
|
516
595
|
def get(
|
517
596
|
self,
|
@@ -522,7 +601,7 @@ class Client:
|
|
522
601
|
*,
|
523
602
|
headers: dict[str, str] | None = None,
|
524
603
|
**extra: Any,
|
525
|
-
) ->
|
604
|
+
) -> ClientResponse:
|
526
605
|
"""Request a response from the server using GET."""
|
527
606
|
self.extra = extra
|
528
607
|
self.headers = headers
|
@@ -548,7 +627,7 @@ class Client:
|
|
548
627
|
*,
|
549
628
|
headers: dict[str, str] | None = None,
|
550
629
|
**extra: Any,
|
551
|
-
) ->
|
630
|
+
) -> ClientResponse:
|
552
631
|
"""Request a response from the server using POST."""
|
553
632
|
self.extra = extra
|
554
633
|
self.headers = headers
|
@@ -578,7 +657,7 @@ class Client:
|
|
578
657
|
*,
|
579
658
|
headers: dict[str, str] | None = None,
|
580
659
|
**extra: Any,
|
581
|
-
) ->
|
660
|
+
) -> ClientResponse:
|
582
661
|
"""Request a response from the server using HEAD."""
|
583
662
|
self.extra = extra
|
584
663
|
self.headers = headers
|
@@ -604,7 +683,7 @@ class Client:
|
|
604
683
|
*,
|
605
684
|
headers: dict[str, str] | None = None,
|
606
685
|
**extra: Any,
|
607
|
-
) ->
|
686
|
+
) -> ClientResponse:
|
608
687
|
"""Request a response from the server using OPTIONS."""
|
609
688
|
self.extra = extra
|
610
689
|
self.headers = headers
|
@@ -635,7 +714,7 @@ class Client:
|
|
635
714
|
*,
|
636
715
|
headers: dict[str, str] | None = None,
|
637
716
|
**extra: Any,
|
638
|
-
) ->
|
717
|
+
) -> ClientResponse:
|
639
718
|
"""Send a resource to the server using PUT."""
|
640
719
|
self.extra = extra
|
641
720
|
self.headers = headers
|
@@ -666,7 +745,7 @@ class Client:
|
|
666
745
|
*,
|
667
746
|
headers: dict[str, str] | None = None,
|
668
747
|
**extra: Any,
|
669
|
-
) ->
|
748
|
+
) -> ClientResponse:
|
670
749
|
"""Send a resource to the server using PATCH."""
|
671
750
|
self.extra = extra
|
672
751
|
self.headers = headers
|
@@ -697,7 +776,7 @@ class Client:
|
|
697
776
|
*,
|
698
777
|
headers: dict[str, str] | None = None,
|
699
778
|
**extra: Any,
|
700
|
-
) ->
|
779
|
+
) -> ClientResponse:
|
701
780
|
"""Send a DELETE request to the server."""
|
702
781
|
self.extra = extra
|
703
782
|
self.headers = headers
|
@@ -727,7 +806,7 @@ class Client:
|
|
727
806
|
*,
|
728
807
|
headers: dict[str, str] | None = None,
|
729
808
|
**extra: Any,
|
730
|
-
) ->
|
809
|
+
) -> ClientResponse:
|
731
810
|
"""Send a TRACE request to the server."""
|
732
811
|
self.extra = extra
|
733
812
|
self.headers = headers
|
@@ -745,16 +824,16 @@ class Client:
|
|
745
824
|
|
746
825
|
def _handle_redirects(
|
747
826
|
self,
|
748
|
-
response:
|
827
|
+
response: ClientResponse,
|
749
828
|
data: Any = "",
|
750
829
|
content_type: str = "",
|
751
830
|
headers: dict[str, str] | None = None,
|
752
831
|
**extra: Any,
|
753
|
-
) ->
|
832
|
+
) -> ClientResponse:
|
754
833
|
"""
|
755
834
|
Follow any redirects by requesting responses from the server using GET.
|
756
835
|
"""
|
757
|
-
response.redirect_chain = []
|
836
|
+
response.redirect_chain = []
|
758
837
|
redirect_status_codes = (
|
759
838
|
HTTPStatus.MOVED_PERMANENTLY,
|
760
839
|
HTTPStatus.FOUND,
|
@@ -763,8 +842,8 @@ class Client:
|
|
763
842
|
HTTPStatus.PERMANENT_REDIRECT,
|
764
843
|
)
|
765
844
|
while response.status_code in redirect_status_codes:
|
766
|
-
response_url = response.url
|
767
|
-
redirect_chain = response.redirect_chain
|
845
|
+
response_url = response.url
|
846
|
+
redirect_chain = response.redirect_chain
|
768
847
|
redirect_chain.append((response_url, response.status_code))
|
769
848
|
|
770
849
|
url = urlsplit(response_url)
|
@@ -781,7 +860,7 @@ class Client:
|
|
781
860
|
path = "/"
|
782
861
|
# Prepend the request path to handle relative path redirects
|
783
862
|
if not path.startswith("/"):
|
784
|
-
path = urljoin(response.request["PATH_INFO"], path)
|
863
|
+
path = urljoin(response.request["PATH_INFO"], path)
|
785
864
|
|
786
865
|
if response.status_code in (
|
787
866
|
HTTPStatus.TEMPORARY_REDIRECT,
|
@@ -789,7 +868,7 @@ class Client:
|
|
789
868
|
):
|
790
869
|
# Preserve request method and query string (if needed)
|
791
870
|
# post-redirect for 307/308 responses.
|
792
|
-
request_method_name = response.request["REQUEST_METHOD"].lower()
|
871
|
+
request_method_name = response.request["REQUEST_METHOD"].lower()
|
793
872
|
if request_method_name not in ("get", "head"):
|
794
873
|
extra["QUERY_STRING"] = url.query
|
795
874
|
request_method = getattr(self, request_method_name)
|
@@ -806,7 +885,7 @@ class Client:
|
|
806
885
|
headers=headers,
|
807
886
|
**extra,
|
808
887
|
)
|
809
|
-
response.redirect_chain = redirect_chain
|
888
|
+
response.redirect_chain = redirect_chain
|
810
889
|
|
811
890
|
if redirect_chain[-1] in redirect_chain[:-1]:
|
812
891
|
# Check that we're not redirecting to somewhere we've already
|
@@ -826,13 +905,8 @@ class Client:
|
|
826
905
|
"""Store exceptions when they are generated by a view."""
|
827
906
|
self.exc_info = sys.exc_info()
|
828
907
|
|
829
|
-
def check_exception(self
|
830
|
-
"""
|
831
|
-
Look for a signaled exception, clear the current context exception
|
832
|
-
data, re-raise the signaled exception, and clear the signaled exception
|
833
|
-
from the local cache.
|
834
|
-
"""
|
835
|
-
response.exc_info = self.exc_info # type: ignore[attr-defined]
|
908
|
+
def check_exception(self) -> None:
|
909
|
+
"""Check for signaled exceptions and potentially re-raise."""
|
836
910
|
if self.exc_info:
|
837
911
|
_, exc_value, _ = self.exc_info
|
838
912
|
self.exc_info = None
|
@@ -856,17 +930,3 @@ class Client:
|
|
856
930
|
from plain.auth.test import logout_client
|
857
931
|
|
858
932
|
logout_client(self)
|
859
|
-
|
860
|
-
def _parse_json(self, response: Response, **extra: Any) -> Any:
|
861
|
-
if not hasattr(response, "_json"):
|
862
|
-
if not _JSON_CONTENT_TYPE_RE.match(response.headers.get("Content-Type")):
|
863
|
-
raise ValueError(
|
864
|
-
'Content-Type header is "{}", not "application/json"'.format(
|
865
|
-
response.headers.get("Content-Type")
|
866
|
-
)
|
867
|
-
)
|
868
|
-
response._json = json.loads( # type: ignore[attr-defined]
|
869
|
-
response.content.decode(response.charset),
|
870
|
-
**extra, # type: ignore[arg-type]
|
871
|
-
)
|
872
|
-
return response._json # type: ignore[attr-defined]
|
plain/views/README.md
CHANGED
@@ -181,7 +181,7 @@ class ExampleDetailView(DetailView):
|
|
181
181
|
def get_object(self):
|
182
182
|
return MyObjectClass.query.get(
|
183
183
|
id=self.url_kwargs["id"],
|
184
|
-
user=self.
|
184
|
+
user=self.user, # Limit access
|
185
185
|
)
|
186
186
|
|
187
187
|
|
@@ -199,7 +199,7 @@ class ExampleUpdateView(UpdateView):
|
|
199
199
|
def get_object(self):
|
200
200
|
return MyObjectClass.query.get(
|
201
201
|
id=self.url_kwargs["id"],
|
202
|
-
user=self.
|
202
|
+
user=self.user, # Limit access
|
203
203
|
)
|
204
204
|
|
205
205
|
|
@@ -213,7 +213,7 @@ class ExampleDeleteView(DeleteView):
|
|
213
213
|
def get_object(self):
|
214
214
|
return MyObjectClass.query.get(
|
215
215
|
id=self.url_kwargs["id"],
|
216
|
-
user=self.
|
216
|
+
user=self.user, # Limit access
|
217
217
|
)
|
218
218
|
|
219
219
|
|
@@ -222,7 +222,7 @@ class ExampleListView(ListView):
|
|
222
222
|
|
223
223
|
def get_objects(self):
|
224
224
|
return MyObjectClass.query.filter(
|
225
|
-
user=self.
|
225
|
+
user=self.user, # Limit access
|
226
226
|
)
|
227
227
|
```
|
228
228
|
|
@@ -241,7 +241,7 @@ from plain.http import Response
|
|
241
241
|
|
242
242
|
class ExampleView(DetailView):
|
243
243
|
def get_object(self):
|
244
|
-
if self.
|
244
|
+
if self.user and self.user.exceeds_rate_limit:
|
245
245
|
raise ResponseException(
|
246
246
|
Response("Rate limit exceeded", status_code=429)
|
247
247
|
)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
plain/AGENTS.md,sha256=As6EFSWWHJ9lYIxb2LMRqNRteH45SRs7a_VFslzF53M,1046
|
2
|
-
plain/CHANGELOG.md,sha256=
|
2
|
+
plain/CHANGELOG.md,sha256=m-g1Jycq3u9KeMyMUD4pSjJhubEoefzEaPvhppCaCcM,22837
|
3
3
|
plain/README.md,sha256=5BJyKhf0TDanWVbOQyZ3zsi5Lov9xk-LlJYCDWofM6Y,4078
|
4
4
|
plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
|
5
5
|
plain/debug.py,sha256=C2OnFHtRGMrpCiHSt-P2r58JypgQZ62qzDBpV4mhtFM,855
|
@@ -70,9 +70,9 @@ plain/internal/files/uploadedfile.py,sha256=RaMeOMMB5LhH_QTEda9fGcI4kEg5CgCLE3kT
|
|
70
70
|
plain/internal/files/uploadhandler.py,sha256=zUEMePuCsoaukRMCy5yBvMHaeOBageUw2sLBHsXpmew,7982
|
71
71
|
plain/internal/files/utils.py,sha256=9aWCkGGGRdZLbI921IOgeUOD9zxx4FgpWCzXvq0lMXU,2871
|
72
72
|
plain/internal/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
|
-
plain/internal/handlers/base.py,sha256=
|
73
|
+
plain/internal/handlers/base.py,sha256=nAK2Wfw1-f3fbsCp8w8zzHwl-Fb8sj9CXWPyMM0aLbk,6551
|
74
74
|
plain/internal/handlers/exception.py,sha256=9Qf9dfQANuaeNx9-DMFJzg3Y3un61NicxfK7YnK3RTk,5226
|
75
|
-
plain/internal/handlers/wsgi.py,sha256=
|
75
|
+
plain/internal/handlers/wsgi.py,sha256=d2Hcs4fzTSir6OvtpVromTw0RmmFAqTL4_EaBjoHtNU,8919
|
76
76
|
plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
77
77
|
plain/internal/middleware/headers.py,sha256=WM46oSTlmPsysbstOUoQLV8vsfGxbT9UpjYqEsw0FFQ,1200
|
78
78
|
plain/internal/middleware/hosts.py,sha256=veh42e1JRNwegP4dVx_urQroEC857YAKt3ThDHyr9Rc,6017
|
@@ -88,7 +88,7 @@ plain/logs/utils.py,sha256=BuHFynr9Oy8R7LzN3WycBvDY1lNX8tAxJ3TBsnchb0k,1628
|
|
88
88
|
plain/packages/README.md,sha256=iNqMtwFDVNf2TqKUzLKQW5Y4_GsssmdB4cVerzu27Ro,2674
|
89
89
|
plain/packages/__init__.py,sha256=OpQny0xLplPdPpozVUUkrW2gB-IIYyDT1b4zMzOcCC4,160
|
90
90
|
plain/packages/config.py,sha256=dxs_i-z6noQF_6j3lq11mhnQ1Bj10u2CXTJY0JgeQgc,3166
|
91
|
-
plain/packages/registry.py,sha256=
|
91
|
+
plain/packages/registry.py,sha256=bmqY1rQau4MRpbf6DXkUVlqF4XJ9Mz2awSi5E7tCGd4,9032
|
92
92
|
plain/preflight/README.md,sha256=vR43F_ls81hRSo7J2NNZ4VOMoRaJ1bS5JwA6l4ez36g,1782
|
93
93
|
plain/preflight/__init__.py,sha256=-uBIVLD1DlJUVypQsEcrOtaNAhECbOpKhyoz0c_WMhA,416
|
94
94
|
plain/preflight/checks.py,sha256=kJcr-Hq5bsjKw1BUYR6r6nFg3Ecgrd1DS5SudUr4rSU,289
|
@@ -118,7 +118,7 @@ plain/templates/jinja/filters.py,sha256=g70cw1jzvYco2v-u4SeceOWBX_qxHI5k9AODMn8e
|
|
118
118
|
plain/templates/jinja/globals.py,sha256=TXl6uObqis_KXYP-jL3SvwqhATaoc7_hU8_fwpBMXyk,570
|
119
119
|
plain/test/README.md,sha256=tNzaVjma0sgowIrViJguCgVy8A2d8mUKApZO2RxTYyU,1140
|
120
120
|
plain/test/__init__.py,sha256=MhNHtp7MYBl9kq-pMRGY11kJ6kU1I6vOkjNkit1TYRg,94
|
121
|
-
plain/test/client.py,sha256=
|
121
|
+
plain/test/client.py,sha256=wgmZOv8kyBDBDq6wamlFdVrmVCfWn5YD62dtARIpILA,31714
|
122
122
|
plain/test/encoding.py,sha256=txj_FCbC4GxH-JCkopW5LaZz8cGsrKQiculjFkjkzuY,3372
|
123
123
|
plain/test/exceptions.py,sha256=Cn4cauBelCiZPnbIXru-zKePXEQn-dit8M4v74C_dTk,492
|
124
124
|
plain/urls/README.md,sha256=026RkCK6I0GdqK3RE2QBLcCLIsiwtyKxgI2F0KBX95E,3882
|
@@ -153,7 +153,7 @@ plain/utils/text.py,sha256=teav7elbqEtGnhKG3ajf-V9Hb-Gsg8uqDrogqWizqjI,10094
|
|
153
153
|
plain/utils/timesince.py,sha256=a_-ZoPK_s3Pt998CW4rWp0clZ1XyK2x04hCqak2giII,5928
|
154
154
|
plain/utils/timezone.py,sha256=M_I5yvs9NsHbtNBPJgHErvWw9vatzx4M96tRQs5gS3g,6823
|
155
155
|
plain/utils/tree.py,sha256=rj_JpZ2kVD3UExWoKnsRdVCoRjvzkuVOONcHzREjSyw,4766
|
156
|
-
plain/views/README.md,sha256=
|
156
|
+
plain/views/README.md,sha256=6mcoSQp60n8qgoIMNDQr29WThpi-NCj8EMqxPNwWpiE,7189
|
157
157
|
plain/views/__init__.py,sha256=a-N1nkklVohJTtz0yD1MMaS0g66HviEjsKydNVVjvVc,392
|
158
158
|
plain/views/base.py,sha256=fk9zAY5BMVBeM45dWL7A9BMTdUi6eTFMeVDd5kBVdv8,4478
|
159
159
|
plain/views/errors.py,sha256=tHD7MNnZcMyiQ46RMAnX1Ne3Zbbkr1zAiVfJyaaLtSQ,1447
|
@@ -162,8 +162,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
|
|
162
162
|
plain/views/objects.py,sha256=5y0PoPPo07dQTTcJ_9kZcx0iI1O7regsooAIK4VqXQ0,5579
|
163
163
|
plain/views/redirect.py,sha256=mIpSAFcaEyeLDyv4Fr6g_ektduG4Wfa6s6L-rkdazmM,2154
|
164
164
|
plain/views/templates.py,sha256=9LgDMCv4C7JzLiyw8jHF-i4350ukwgixC_9y4faEGu0,1885
|
165
|
-
plain-0.72.
|
166
|
-
plain-0.72.
|
167
|
-
plain-0.72.
|
168
|
-
plain-0.72.
|
169
|
-
plain-0.72.
|
165
|
+
plain-0.72.2.dist-info/METADATA,sha256=oNUbDDi3O3AiTeJuuZ_tPo1D5tBhEkzhNIligFBg-kE,4488
|
166
|
+
plain-0.72.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
167
|
+
plain-0.72.2.dist-info/entry_points.txt,sha256=wvMzY-iREvfqRgyLm77htPp4j_8CQslLoZA15_AnNo8,171
|
168
|
+
plain-0.72.2.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
169
|
+
plain-0.72.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|