plain 0.57.0__py3-none-any.whl → 0.59.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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # plain changelog
2
2
 
3
+ ## [0.59.0](https://github.com/dropseed/plain/releases/plain@0.59.0) (2025-08-22)
4
+
5
+ ### What's changed
6
+
7
+ - Added new `APP_NAME` setting that defaults to the project name from `pyproject.toml` ([1a4d60e](https://github.com/dropseed/plain/commit/1a4d60e787))
8
+ - Template views now validate that `get_template_names()` returns a list instead of a string ([428a64f](https://github.com/dropseed/plain/commit/428a64f8cc))
9
+ - Object views now use cached properties for `.object` and `.objects` to improve performance ([bd0507a](https://github.com/dropseed/plain/commit/bd0507a72c))
10
+ - Improved `plain upgrade` command to suggest using subagents when there are more than 3 package updates ([497c30d](https://github.com/dropseed/plain/commit/497c30d445))
11
+
12
+ ### Upgrade instructions
13
+
14
+ - In object views, `self.load_object()` is no longer necessary as `self.object` is now a cached property.
15
+
16
+ ## [0.58.0](https://github.com/dropseed/plain/releases/plain@0.58.0) (2025-08-19)
17
+
18
+ ### What's changed
19
+
20
+ - Complete rewrite of CSRF protection using modern Sec-Fetch-Site headers and origin validation ([955150800c](https://github.com/dropseed/plain/commit/955150800c))
21
+ - Replaced CSRF view mixin with path-based exemptions using `CSRF_EXEMPT_PATHS` setting ([2a50a9154e](https://github.com/dropseed/plain/commit/2a50a9154e))
22
+ - Renamed `HTTPS_REDIRECT_EXEMPT` to `HTTPS_REDIRECT_EXEMPT_PATHS` with leading slash requirement ([b53d3bb7a7](https://github.com/dropseed/plain/commit/b53d3bb7a7))
23
+ - Agent commands now print prompts directly when running in Claude Code or Codex Sandbox environments ([6eaed8ae3b](https://github.com/dropseed/plain/commit/6eaed8ae3b))
24
+
25
+ ### Upgrade instructions
26
+
27
+ - Remove any usage of `CsrfExemptViewMixin` and `request.csrf_exempt` and add exempt paths to the `CSRF_EXEMPT_PATHS` setting instead (ex. `CSRF_EXEMPT_PATHS = [r"^/api/", r"/webhooks/.*"]` -- but consider first whether the view still needs CSRF exemption under the new implementation)
28
+ - Replace `HTTPS_REDIRECT_EXEMPT` with `HTTPS_REDIRECT_EXEMPT_PATHS` and ensure patterns include leading slash (ex. `[r"^/health$", r"/api/internal/.*"]`)
29
+ - Remove all CSRF cookie and token related settings - the new implementation doesn't use cookies or tokens (ex. `{{ csrf_input }}` and `{{ csrf_token }}`)
30
+
3
31
  ## [0.57.0](https://github.com/dropseed/plain/releases/plain@0.57.0) (2025-08-15)
4
32
 
5
33
  ### What's changed
plain/cli/agent.py CHANGED
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import shlex
2
3
  import subprocess
3
4
 
@@ -20,6 +21,11 @@ def prompt_agent(
20
21
  True if the agent command succeeded (or no agent command was provided),
21
22
  False if the agent command failed.
22
23
  """
24
+ # Check if running inside an agent and just print the prompt if so
25
+ if os.environ.get("CLAUDECODE") or os.environ.get("CODEX_SANDBOX"):
26
+ click.echo(prompt)
27
+ return True
28
+
23
29
  if print_only or not agent_command:
24
30
  click.echo(prompt)
25
31
  if not print_only:
plain/cli/upgrade.py CHANGED
@@ -158,6 +158,7 @@ def build_prompt(before_after: dict[str, tuple[str | None, str | None]]) -> str:
158
158
  "",
159
159
  "3. **Available tools:**",
160
160
  " - Python shell: `uv run python`",
161
+ " - If you have a subagents feature and there are more than three packages here, use subagents",
161
162
  "",
162
163
  "4. **Workflow:**",
163
164
  " - Review changelog for each package → Apply changes → Move to next package",
plain/csrf/README.md CHANGED
@@ -1,23 +1,75 @@
1
1
  # CSRF
2
2
 
3
- **Cross-Site Request Forgery (CSRF) protection.**
3
+ **Cross-Site Request Forgery (CSRF) protection using modern request headers.**
4
4
 
5
5
  - [Overview](#overview)
6
6
  - [Usage](#usage)
7
+ - [CSRF Exempt Paths](#csrf-exempt-paths)
8
+ - [Trusted Origins](#trusted-origins)
7
9
 
8
10
  ## Overview
9
11
 
10
- Plain protects against [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery) through a [middleware](./middleware.py#CsrfViewMiddleware) that compares the generated `csrftoken` cookie with the CSRF token from the request (either `_csrftoken` in form data or the `CSRF-Token` header).
12
+ Plain provides modern CSRF protection based on [Filippo Valsorda's 2025 research](https://words.filippo.io/csrf/) using `Sec-Fetch-Site` headers and origin validation.
11
13
 
12
14
  ## Usage
13
15
 
14
- The `CsrfViewMiddleware` is [automatically installed](../internal/handlers/base.py#BUILTIN_BEFORE_MIDDLEWARE), so you don't need to add it to your `settings.MIDDLEWARE`.
16
+ The `CsrfViewMiddleware` is [automatically installed](../internal/handlers/base.py#BUILTIN_BEFORE_MIDDLEWARE) and works transparently. **No changes to your forms or templates are needed.**
15
17
 
16
- When you use HTML forms, you should include the CSRF token in the form data via a hidden input:
18
+ ## CSRF Exempt Paths
17
19
 
18
- ```html
19
- <form method="post">
20
- {{ csrf_input }}
21
- <!-- other form fields here -->
22
- </form>
20
+ In some cases, you may need to disable CSRF protection for specific paths (like API endpoints or webhooks). Configure exempt paths using regex patterns in your settings:
21
+
22
+ ```python
23
+ # settings.py
24
+ CSRF_EXEMPT_PATHS = [
25
+ r"^/api/", # All API endpoints
26
+ r"^/api/v\d+/", # Versioned APIs: /api/v1/, /api/v2/, etc.
27
+ r"/webhooks/.*", # All webhook paths
28
+ r"/webhooks/github/", # Specific webhook
29
+ r"/health$", # Exact match for /health endpoint
30
+ ]
23
31
  ```
32
+
33
+ **Pattern Matching**: Exempt paths use Python regex patterns with `re.search()` against the full URL path including the leading slash.
34
+
35
+ **Examples:**
36
+
37
+ - `r"^/api/"` - matches `/api/users/`, `/api/posts/`
38
+ - `r"/webhooks/.*"` - matches `/webhooks/github/push`, `/webhooks/stripe/payment`
39
+ - `r"/health$"` - matches `/health` but not `/health-check`
40
+ - `r"^/api/v\d+/"` - matches `/api/v1/users/`, `/api/v2/posts/`
41
+
42
+ **Common Use Cases:**
43
+
44
+ ```python
45
+ CSRF_EXEMPT_PATHS = [
46
+ # API endpoints (often consumed by JavaScript/mobile apps)
47
+ r"^/api/",
48
+
49
+ # Webhooks (external services posting data)
50
+ r"/webhooks/.*",
51
+
52
+ # Health checks and monitoring
53
+ r"/health$",
54
+ r"/status$",
55
+ r"/metrics$",
56
+
57
+ # File uploads (if using direct POST)
58
+ r"/upload/",
59
+ ]
60
+ ```
61
+
62
+ ## Trusted Origins
63
+
64
+ In some cases, you may need to allow requests from specific external origins (like API clients or mobile apps). You can configure trusted origins in your settings:
65
+
66
+ ```python
67
+ # settings.py
68
+ CSRF_TRUSTED_ORIGINS = [
69
+ "https://api.example.com",
70
+ "https://mobile.example.com:8443",
71
+ "https://trusted-partner.com",
72
+ ]
73
+ ```
74
+
75
+ **Important**: Trusted origins bypass **all** CSRF protection. Only add origins you completely trust, as they can make requests that appear to come from your users.