plain 0.66.0__py3-none-any.whl → 0.101.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 +684 -0
- plain/README.md +1 -1
- plain/assets/compile.py +25 -12
- plain/assets/finders.py +24 -17
- plain/assets/fingerprints.py +10 -7
- plain/assets/urls.py +1 -1
- plain/assets/views.py +47 -33
- plain/chores/README.md +25 -23
- plain/chores/__init__.py +2 -1
- plain/chores/core.py +27 -0
- plain/chores/registry.py +23 -53
- plain/cli/README.md +185 -16
- plain/cli/__init__.py +2 -1
- plain/cli/agent.py +236 -0
- plain/cli/build.py +7 -8
- plain/cli/changelog.py +11 -5
- plain/cli/chores.py +32 -34
- plain/cli/core.py +112 -28
- plain/cli/docs.py +52 -11
- plain/cli/formatting.py +40 -17
- plain/cli/install.py +10 -54
- plain/cli/{agent/llmdocs.py → llmdocs.py} +21 -9
- plain/cli/output.py +6 -2
- plain/cli/preflight.py +175 -102
- plain/cli/print.py +4 -4
- plain/cli/registry.py +95 -26
- plain/cli/request.py +206 -0
- plain/cli/runtime.py +45 -0
- plain/cli/scaffold.py +2 -7
- plain/cli/server.py +153 -0
- plain/cli/settings.py +53 -49
- plain/cli/shell.py +15 -12
- plain/cli/startup.py +9 -8
- plain/cli/upgrade.py +17 -104
- plain/cli/urls.py +12 -7
- plain/cli/utils.py +3 -3
- plain/csrf/README.md +65 -40
- plain/csrf/middleware.py +53 -43
- plain/debug.py +5 -2
- plain/exceptions.py +22 -114
- plain/forms/README.md +453 -24
- plain/forms/__init__.py +55 -4
- plain/forms/boundfield.py +15 -8
- plain/forms/exceptions.py +1 -1
- plain/forms/fields.py +346 -143
- plain/forms/forms.py +75 -45
- plain/http/README.md +356 -9
- plain/http/__init__.py +41 -26
- plain/http/cookie.py +15 -7
- plain/http/exceptions.py +65 -0
- plain/http/middleware.py +32 -0
- plain/http/multipartparser.py +99 -88
- plain/http/request.py +362 -250
- plain/http/response.py +99 -197
- plain/internal/__init__.py +8 -1
- plain/internal/files/base.py +35 -19
- plain/internal/files/locks.py +19 -11
- plain/internal/files/move.py +8 -3
- plain/internal/files/temp.py +25 -6
- plain/internal/files/uploadedfile.py +47 -28
- plain/internal/files/uploadhandler.py +64 -58
- plain/internal/files/utils.py +24 -10
- plain/internal/handlers/base.py +34 -23
- plain/internal/handlers/exception.py +68 -65
- plain/internal/handlers/wsgi.py +65 -54
- plain/internal/middleware/headers.py +37 -11
- plain/internal/middleware/hosts.py +11 -13
- plain/internal/middleware/https.py +17 -7
- plain/internal/middleware/slash.py +14 -9
- plain/internal/reloader.py +77 -0
- plain/json.py +2 -1
- plain/logs/README.md +161 -62
- plain/logs/__init__.py +1 -1
- plain/logs/{loggers.py → app.py} +71 -67
- plain/logs/configure.py +63 -14
- plain/logs/debug.py +17 -6
- plain/logs/filters.py +15 -0
- plain/logs/formatters.py +7 -4
- plain/packages/README.md +105 -23
- plain/packages/config.py +15 -7
- plain/packages/registry.py +40 -15
- plain/paginator.py +31 -21
- plain/preflight/README.md +208 -23
- plain/preflight/__init__.py +5 -24
- plain/preflight/checks.py +12 -0
- plain/preflight/files.py +19 -13
- plain/preflight/registry.py +80 -58
- plain/preflight/results.py +37 -0
- plain/preflight/security.py +65 -71
- plain/preflight/settings.py +54 -0
- plain/preflight/urls.py +10 -48
- plain/runtime/README.md +115 -47
- plain/runtime/__init__.py +10 -6
- plain/runtime/global_settings.py +43 -33
- plain/runtime/secret.py +20 -0
- plain/runtime/user_settings.py +110 -38
- plain/runtime/utils.py +1 -1
- plain/server/LICENSE +35 -0
- plain/server/README.md +155 -0
- plain/server/__init__.py +9 -0
- plain/server/app.py +52 -0
- plain/server/arbiter.py +555 -0
- plain/server/config.py +118 -0
- plain/server/errors.py +31 -0
- plain/server/glogging.py +292 -0
- plain/server/http/__init__.py +12 -0
- plain/server/http/body.py +283 -0
- plain/server/http/errors.py +155 -0
- plain/server/http/message.py +400 -0
- plain/server/http/parser.py +70 -0
- plain/server/http/unreader.py +88 -0
- plain/server/http/wsgi.py +421 -0
- plain/server/pidfile.py +92 -0
- plain/server/sock.py +240 -0
- plain/server/util.py +317 -0
- plain/server/workers/__init__.py +6 -0
- plain/server/workers/base.py +304 -0
- plain/server/workers/sync.py +212 -0
- plain/server/workers/thread.py +399 -0
- plain/server/workers/workertmp.py +50 -0
- plain/signals/README.md +170 -1
- plain/signals/__init__.py +0 -1
- plain/signals/dispatch/dispatcher.py +49 -27
- plain/signing.py +131 -35
- plain/skills/README.md +36 -0
- plain/skills/plain-docs/SKILL.md +25 -0
- plain/skills/plain-install/SKILL.md +26 -0
- plain/skills/plain-request/SKILL.md +39 -0
- plain/skills/plain-shell/SKILL.md +24 -0
- plain/skills/plain-upgrade/SKILL.md +35 -0
- plain/templates/README.md +211 -20
- plain/templates/jinja/__init__.py +14 -27
- plain/templates/jinja/environments.py +5 -4
- plain/templates/jinja/extensions.py +12 -5
- plain/templates/jinja/filters.py +7 -2
- plain/templates/jinja/globals.py +2 -2
- plain/test/README.md +184 -22
- plain/test/client.py +340 -222
- plain/test/encoding.py +9 -6
- plain/test/exceptions.py +7 -2
- plain/urls/README.md +157 -73
- plain/urls/converters.py +18 -15
- plain/urls/exceptions.py +2 -2
- plain/urls/patterns.py +56 -40
- plain/urls/resolvers.py +38 -28
- plain/urls/utils.py +5 -1
- plain/utils/README.md +250 -3
- plain/utils/cache.py +17 -11
- plain/utils/crypto.py +21 -5
- plain/utils/datastructures.py +89 -56
- plain/utils/dateparse.py +9 -6
- plain/utils/deconstruct.py +15 -7
- plain/utils/decorators.py +5 -1
- plain/utils/dotenv.py +373 -0
- plain/utils/duration.py +8 -4
- plain/utils/encoding.py +14 -7
- plain/utils/functional.py +66 -49
- plain/utils/hashable.py +5 -1
- plain/utils/html.py +36 -22
- plain/utils/http.py +16 -9
- plain/utils/inspect.py +14 -6
- plain/utils/ipv6.py +7 -3
- plain/utils/itercompat.py +6 -1
- plain/utils/module_loading.py +7 -3
- plain/utils/regex_helper.py +37 -23
- plain/utils/safestring.py +14 -6
- plain/utils/text.py +41 -23
- plain/utils/timezone.py +33 -22
- plain/utils/tree.py +35 -19
- plain/validators.py +94 -52
- plain/views/README.md +156 -79
- plain/views/__init__.py +0 -1
- plain/views/base.py +25 -18
- plain/views/errors.py +13 -5
- plain/views/exceptions.py +4 -1
- plain/views/forms.py +6 -6
- plain/views/objects.py +52 -49
- plain/views/redirect.py +18 -15
- plain/views/templates.py +5 -3
- plain/wsgi.py +3 -1
- {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/METADATA +4 -2
- plain-0.101.2.dist-info/RECORD +201 -0
- {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/WHEEL +1 -1
- plain-0.101.2.dist-info/entry_points.txt +2 -0
- plain/AGENTS.md +0 -18
- plain/cli/agent/__init__.py +0 -20
- plain/cli/agent/docs.py +0 -80
- plain/cli/agent/md.py +0 -87
- plain/cli/agent/prompt.py +0 -45
- plain/cli/agent/request.py +0 -181
- plain/csrf/views.py +0 -31
- plain/logs/utils.py +0 -46
- plain/preflight/messages.py +0 -81
- plain/templates/AGENTS.md +0 -3
- plain-0.66.0.dist-info/RECORD +0 -168
- plain-0.66.0.dist-info/entry_points.txt +0 -4
- {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/licenses/LICENSE +0 -0
plain/test/README.md
CHANGED
|
@@ -1,42 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# plain.test
|
|
2
2
|
|
|
3
|
-
**Testing utilities for
|
|
3
|
+
**Testing utilities for making HTTP requests and inspecting responses.**
|
|
4
4
|
|
|
5
5
|
- [Overview](#overview)
|
|
6
|
+
- [Making requests](#making-requests)
|
|
7
|
+
- [GET requests](#get-requests)
|
|
8
|
+
- [POST requests](#post-requests)
|
|
9
|
+
- [Other HTTP methods](#other-http-methods)
|
|
10
|
+
- [Following redirects](#following-redirects)
|
|
11
|
+
- [Custom headers](#custom-headers)
|
|
12
|
+
- [Inspecting responses](#inspecting-responses)
|
|
13
|
+
- [JSON responses](#json-responses)
|
|
14
|
+
- [Response attributes](#response-attributes)
|
|
15
|
+
- [Authentication](#authentication)
|
|
16
|
+
- [Sessions](#sessions)
|
|
17
|
+
- [RequestFactory](#requestfactory)
|
|
18
|
+
- [FAQs](#faqs)
|
|
19
|
+
- [What is the difference between Client and RequestFactory?](#what-is-the-difference-between-client-and-requestfactory)
|
|
20
|
+
- [How do I test file uploads?](#how-do-i-test-file-uploads)
|
|
21
|
+
- [How do I disable exception raising?](#how-do-i-disable-exception-raising)
|
|
22
|
+
- [Installation](#installation)
|
|
6
23
|
|
|
7
24
|
## Overview
|
|
8
25
|
|
|
9
|
-
|
|
26
|
+
You can test your Plain views using the [`Client`](./client.py#Client) class. It simulates HTTP requests and returns responses, allowing you to verify status codes, content, and behavior without running a real server.
|
|
10
27
|
|
|
11
28
|
```python
|
|
12
29
|
from plain.test import Client
|
|
13
30
|
|
|
14
31
|
|
|
15
|
-
def
|
|
32
|
+
def test_homepage():
|
|
16
33
|
client = Client()
|
|
17
|
-
|
|
18
|
-
# Getting responses
|
|
19
34
|
response = client.get("/")
|
|
20
35
|
assert response.status_code == 200
|
|
36
|
+
```
|
|
21
37
|
|
|
22
|
-
|
|
23
|
-
client.session["example"] = "value"
|
|
24
|
-
assert client.session["example"] == "value"
|
|
38
|
+
The client maintains cookies and session state across requests, so you can test multi-step flows like login and logout.
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
## Making requests
|
|
41
|
+
|
|
42
|
+
### GET requests
|
|
43
|
+
|
|
44
|
+
Pass query parameters using the `data` argument.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
response = client.get("/search/", data={"q": "hello"})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### POST requests
|
|
51
|
+
|
|
52
|
+
Send form data by default.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
response = client.post("/submit/", data={"name": "Alice", "email": "alice@example.com"})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Send JSON by setting the content type.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
response = client.post(
|
|
62
|
+
"/api/users/",
|
|
63
|
+
data={"name": "Alice"},
|
|
64
|
+
content_type="application/json",
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Other HTTP methods
|
|
69
|
+
|
|
70
|
+
The client supports all standard HTTP methods: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, and `trace`.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
response = client.put("/api/users/1/", data={"name": "Bob"}, content_type="application/json")
|
|
74
|
+
response = client.patch("/api/users/1/", data={"name": "Bob"}, content_type="application/json")
|
|
75
|
+
response = client.delete("/api/users/1/")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Following redirects
|
|
79
|
+
|
|
80
|
+
Set `follow=True` to automatically follow redirect responses.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
response = client.get("/old-url/", follow=True)
|
|
84
|
+
assert response.status_code == 200 # Final destination
|
|
85
|
+
assert response.redirect_chain == [("/new-url/", 302)]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom headers
|
|
89
|
+
|
|
90
|
+
Pass headers using the `headers` argument.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
response = client.get("/api/", headers={"Authorization": "Bearer token123"})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can also set default headers when creating the client.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
client = Client(headers={"Accept-Language": "en-US"})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Inspecting responses
|
|
103
|
+
|
|
104
|
+
### JSON responses
|
|
105
|
+
|
|
106
|
+
Parse JSON response content using the `json()` method.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
response = client.get("/api/users/")
|
|
110
|
+
data = response.json()
|
|
111
|
+
assert data["users"][0]["name"] == "Alice"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Response attributes
|
|
115
|
+
|
|
116
|
+
The [`ClientResponse`](./client.py#ClientResponse) wrapper provides access to:
|
|
117
|
+
|
|
118
|
+
- `status_code` - HTTP status code (200, 404, etc.)
|
|
119
|
+
- `content` - Response body as bytes
|
|
120
|
+
- `headers` - Response headers
|
|
121
|
+
- `cookies` - Cookies set by the response
|
|
122
|
+
- `wsgi_request` - The original request object
|
|
123
|
+
- `resolver_match` - URL resolver match information
|
|
124
|
+
- `redirect_chain` - List of redirects when using `follow=True`
|
|
125
|
+
|
|
126
|
+
## Authentication
|
|
127
|
+
|
|
128
|
+
You can log in a user without going through the login flow using `force_login`. This requires `plain.auth` to be installed.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
user = User.objects.get(username="alice")
|
|
132
|
+
client.force_login(user)
|
|
133
|
+
response = client.get("/dashboard/")
|
|
134
|
+
assert response.status_code == 200
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Log out using the `logout` method.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
client.logout()
|
|
141
|
+
response = client.get("/dashboard/")
|
|
142
|
+
assert response.status_code == 302 # Redirected to login
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Sessions
|
|
146
|
+
|
|
147
|
+
Access session data using the `session` property. This requires `plain.sessions` to be installed.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
client.session["cart_id"] = "abc123"
|
|
151
|
+
response = client.get("/cart/")
|
|
152
|
+
assert "abc123" in response.content.decode()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## RequestFactory
|
|
31
156
|
|
|
32
|
-
|
|
33
|
-
client.logout()
|
|
34
|
-
response = client.get("/protected/")
|
|
35
|
-
assert response.status_code == 302
|
|
157
|
+
Use [`RequestFactory`](./client.py#RequestFactory) to create request objects directly without going through the WSGI handler. This is useful for testing views in isolation.
|
|
36
158
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
159
|
+
```python
|
|
160
|
+
from plain.test import RequestFactory
|
|
161
|
+
|
|
162
|
+
rf = RequestFactory()
|
|
163
|
+
request = rf.get("/hello/")
|
|
164
|
+
response = my_view(request)
|
|
165
|
+
assert response.status_code == 200
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The factory supports the same HTTP methods as the client: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, and `trace`.
|
|
169
|
+
|
|
170
|
+
## FAQs
|
|
171
|
+
|
|
172
|
+
#### What is the difference between Client and RequestFactory?
|
|
173
|
+
|
|
174
|
+
The `Client` executes requests through the full middleware stack and maintains state (cookies, sessions) between requests. Use it for integration tests.
|
|
175
|
+
|
|
176
|
+
The `RequestFactory` creates request objects without executing them. Use it for unit testing individual views in isolation.
|
|
177
|
+
|
|
178
|
+
#### How do I test file uploads?
|
|
179
|
+
|
|
180
|
+
Pass file-like objects in the `data` dictionary.
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from io import BytesIO
|
|
184
|
+
|
|
185
|
+
file = BytesIO(b"file contents")
|
|
186
|
+
file.name = "test.txt"
|
|
187
|
+
response = client.post("/upload/", data={"file": file})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### How do I disable exception raising?
|
|
191
|
+
|
|
192
|
+
By default, the client raises exceptions that occur during request processing. Set `raise_request_exception=False` to capture them on the response instead.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
client = Client(raise_request_exception=False)
|
|
196
|
+
response = client.get("/broken/")
|
|
197
|
+
assert response.status_code == 500
|
|
40
198
|
```
|
|
41
199
|
|
|
42
|
-
|
|
200
|
+
## Installation
|
|
201
|
+
|
|
202
|
+
The `plain.test` module is included with the `plain` package. No additional installation is required.
|
|
203
|
+
|
|
204
|
+
For additional testing utilities like pytest fixtures and browser testing, see [`plain.pytest`](/plain-pytest/README.md).
|