plain 0.48.0__py3-none-any.whl → 0.50.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 ADDED
@@ -0,0 +1,33 @@
1
+ # plain changelog
2
+
3
+ ## [0.50.0](https://github.com/dropseed/plain/releases/plain@0.50.0) (2025-06-23)
4
+
5
+ ### What's changed
6
+
7
+ - The URL inspection command has moved; run `plain urls list` instead of the old `plain urls` command ([6146fcb](https://github.com/dropseed/plain/commit/6146fcba536c551277d625bd750c385431ea18eb))
8
+ - `plain preflight` gains a simpler `--database` flag that enables database checks for your default database. The previous behaviour that accepted one or more database aliases has been removed ([d346d81](https://github.com/dropseed/plain/commit/d346d81567d2cc45bbed93caba18a195de10c572))
9
+ - Settings overhaul: use a single `DATABASE` setting instead of `DATABASES`/`DATABASE_ROUTERS` ([d346d81](https://github.com/dropseed/plain/commit/d346d81567d2cc45bbed93caba18a195de10c572))
10
+
11
+ ### Upgrade instructions
12
+
13
+ - Update any scripts or documentation that call `plain urls …`:
14
+ - Replace `plain urls --flat` with `plain urls list --flat`
15
+ - If you invoke preflight checks in CI or locally:
16
+ - Replace `plain preflight --database <alias>` (or multiple aliases) with the new boolean flag: `plain preflight --database`
17
+ - In `settings.py` migrate to the new database configuration:
18
+ ```python
19
+ # Before
20
+ DATABASES = {
21
+ "default": {
22
+ "ENGINE": "plain.backends.sqlite3",
23
+ "NAME": BASE_DIR / "db.sqlite3",
24
+ }
25
+ }
26
+
27
+ # After
28
+ DATABASE = {
29
+ "ENGINE": "plain.backends.sqlite3",
30
+ "NAME": BASE_DIR / "db.sqlite3",
31
+ }
32
+ ```
33
+ Remove any `DATABASES` and `DATABASE_ROUTERS` settings – they are no longer read.
plain/cli/README.md CHANGED
@@ -30,6 +30,12 @@ Run a Python script in the context of your app.
30
30
 
31
31
  View the runtime value of a named setting.
32
32
 
33
+ ### `plain help`
34
+
35
+ Print help for all available commands and subcommands.
36
+ Each command's help output is prefixed with the full command name for
37
+ readability.
38
+
33
39
  ### `plain shell`
34
40
 
35
41
  Open a Python shell with the Plain loaded.
@@ -46,7 +52,7 @@ __all__ = [
46
52
  ]
47
53
  ```
48
54
 
49
- ### `plain urls`
55
+ ### `plain urls list`
50
56
 
51
57
  List all the URL patterns in your app.
52
58
 
plain/cli/core.py CHANGED
@@ -10,6 +10,7 @@ from .build import build
10
10
  from .chores import chores
11
11
  from .docs import docs
12
12
  from .formatting import PlainContext
13
+ from .help import help_cmd
13
14
  from .preflight import preflight_checks
14
15
  from .registry import cli_registry
15
16
  from .scaffold import create
@@ -34,6 +35,7 @@ plain_cli.add_command(urls)
34
35
  plain_cli.add_command(setting)
35
36
  plain_cli.add_command(shell)
36
37
  plain_cli.add_command(run)
38
+ plain_cli.add_command(help_cmd)
37
39
 
38
40
 
39
41
  class CLIRegistryGroup(click.Group):
plain/cli/help.py ADDED
@@ -0,0 +1,27 @@
1
+ import click
2
+ from click.core import MultiCommand
3
+
4
+
5
+ @click.command("help")
6
+ @click.pass_context
7
+ def help_cmd(ctx):
8
+ """Show help for all commands and subcommands."""
9
+
10
+ root = ctx.parent.command
11
+ info_name = ctx.parent.info_name or "plain"
12
+
13
+ def print_help(cmd, prog, parent=None):
14
+ sub_ctx = click.Context(cmd, info_name=prog, parent=parent)
15
+
16
+ title = sub_ctx.command_path
17
+ click.secho(title, fg="green", bold=True)
18
+ click.secho("-" * len(title), fg="green")
19
+ click.echo(sub_ctx.get_help())
20
+
21
+ if isinstance(cmd, MultiCommand):
22
+ for name in cmd.list_commands(sub_ctx):
23
+ click.echo()
24
+ sub_cmd = cmd.get_command(sub_ctx, name)
25
+ print_help(sub_cmd, name, sub_ctx)
26
+
27
+ print_help(root, info_name)
plain/cli/preflight.py CHANGED
@@ -19,11 +19,10 @@ from plain.packages import packages_registry
19
19
  )
20
20
  @click.option(
21
21
  "--database",
22
- "databases",
23
- multiple=True,
24
- help="Run database related checks against these aliases.",
22
+ is_flag=True,
23
+ help="Run database related checks as part of preflight.",
25
24
  )
26
- def preflight_checks(package_label, deploy, fail_level, databases):
25
+ def preflight_checks(package_label, deploy, fail_level, database):
27
26
  """
28
27
  Use the system check framework to validate entire Plain project.
29
28
  Raise CommandError for any serious message (error or critical errors).
@@ -42,7 +41,7 @@ def preflight_checks(package_label, deploy, fail_level, databases):
42
41
  all_issues = preflight.run_checks(
43
42
  package_configs=package_configs,
44
43
  include_deployment_checks=include_deployment_checks,
45
- databases=databases,
44
+ database=database,
46
45
  )
47
46
 
48
47
  header, body, footer = "", "", ""
plain/cli/urls.py CHANGED
@@ -3,9 +3,15 @@ import sys
3
3
  import click
4
4
 
5
5
 
6
- @click.command()
6
+ @click.group()
7
+ def urls():
8
+ """URL related commands"""
9
+ pass
10
+
11
+
12
+ @urls.command("list")
7
13
  @click.option("--flat", is_flag=True, help="List all URLs in a flat list")
8
- def urls(flat):
14
+ def list_urls(flat):
9
15
  """Print all URL patterns under settings.URLS_ROUTER"""
10
16
  from plain.runtime import settings
11
17
  from plain.urls import URLResolver, get_resolver
@@ -42,7 +42,7 @@ class CheckRegistry:
42
42
  self,
43
43
  package_configs=None,
44
44
  include_deployment_checks=False,
45
- databases=None,
45
+ database=False,
46
46
  ):
47
47
  """
48
48
  Run all registered checks and return list of Errors and Warnings.
@@ -51,7 +51,7 @@ class CheckRegistry:
51
51
  checks = self.get_checks(include_deployment_checks)
52
52
 
53
53
  for check in checks:
54
- new_errors = check(package_configs=package_configs, databases=databases)
54
+ new_errors = check(package_configs=package_configs, database=database)
55
55
  if not is_iterable(new_errors):
56
56
  raise TypeError(
57
57
  f"The function {check!r} did not return a list. All functions "
plain/runtime/README.md CHANGED
@@ -79,28 +79,21 @@ from os import environ
79
79
 
80
80
  from . import database_url
81
81
 
82
- # Make DATABASES a required setting
83
- DATABASES: dict
82
+ # Make DATABASE a required setting
83
+ DATABASE: dict
84
84
 
85
- # Automatically configure DATABASES if a DATABASE_URL was given in the environment
85
+ # Automatically configure DATABASE if a DATABASE_URL was given in the environment
86
86
  if "DATABASE_URL" in environ:
87
- DATABASES = {
88
- "default": database_url.parse_database_url(
89
- environ["DATABASE_URL"],
90
- # Enable persistent connections by default
91
- conn_max_age=int(environ.get("DATABASE_CONN_MAX_AGE", 600)),
92
- conn_health_checks=environ.get(
93
- "DATABASE_CONN_HEALTH_CHECKS", "true"
94
- ).lower()
95
- in [
96
- "true",
97
- "1",
98
- ],
99
- )
100
- }
101
-
102
- # Classes used to implement DB routing behavior.
103
- DATABASE_ROUTERS = []
87
+ DATABASE = database_url.parse_database_url(
88
+ environ["DATABASE_URL"],
89
+ # Enable persistent connections by default
90
+ conn_max_age=int(environ.get("DATABASE_CONN_MAX_AGE", 600)),
91
+ conn_health_checks=environ.get("DATABASE_CONN_HEALTH_CHECKS", "true").lower()
92
+ in [
93
+ "true",
94
+ "1",
95
+ ],
96
+ )
104
97
  ```
105
98
 
106
99
  ### .env files
plain/test/client.py CHANGED
@@ -6,7 +6,7 @@ from http.cookies import SimpleCookie
6
6
  from io import BytesIO, IOBase
7
7
  from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit
8
8
 
9
- from plain.http import HttpHeaders, HttpRequest, QueryDict
9
+ from plain.http import HttpHeaders, QueryDict
10
10
  from plain.internal import internalcode
11
11
  from plain.internal.handlers.base import BaseHandler
12
12
  from plain.internal.handlers.wsgi import WSGIRequest
@@ -775,57 +775,20 @@ class Client(RequestFactory):
775
775
  @property
776
776
  def session(self):
777
777
  """Return the current session variables."""
778
- from plain.sessions import SessionStore
778
+ from plain.sessions.test import get_client_session
779
779
 
780
- cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
781
- if cookie:
782
- return SessionStore(cookie.value)
783
- session = SessionStore()
784
- session.save()
785
- self.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
786
- return session
780
+ return get_client_session(self)
787
781
 
788
782
  def force_login(self, user):
789
- self._login(user)
783
+ from plain.auth.test import login_client
790
784
 
791
- def _login(self, user):
792
- from plain.auth import login
793
- from plain.sessions import SessionStore
794
-
795
- # Create a fake request to store login details.
796
- request = HttpRequest()
797
- if self.session:
798
- request.session = self.session
799
- else:
800
- request.session = SessionStore()
801
- login(request, user)
802
- # Save the session values.
803
- request.session.save()
804
- # Set the cookie to represent the session.
805
- session_cookie = settings.SESSION_COOKIE_NAME
806
- self.cookies[session_cookie] = request.session.session_key
807
- cookie_data = {
808
- "max-age": None,
809
- "path": "/",
810
- "domain": settings.SESSION_COOKIE_DOMAIN,
811
- "secure": settings.SESSION_COOKIE_SECURE or None,
812
- "expires": None,
813
- }
814
- self.cookies[session_cookie].update(cookie_data)
785
+ login_client(self, user)
815
786
 
816
787
  def logout(self):
817
788
  """Log out the user by removing the cookies and session object."""
818
- from plain.auth import get_user, logout
819
- from plain.sessions import SessionStore
789
+ from plain.auth.test import logout_client
820
790
 
821
- request = HttpRequest()
822
- if self.session:
823
- request.session = self.session
824
- request.user = get_user(request)
825
- else:
826
- request.session = SessionStore()
827
- logout(request)
828
- self.cookies = SimpleCookie()
791
+ logout_client(self)
829
792
 
830
793
  def _parse_json(self, response, **extra):
831
794
  if not hasattr(response, "_json"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.48.0
3
+ Version: 0.50.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,3 +1,4 @@
1
+ plain/CHANGELOG.md,sha256=Bl0GLzelbqK3Tyg-dAGBcHG49JwXtH3g6GHFCl-LEpk,1554
1
2
  plain/README.md,sha256=gik6DBZcJAITcm4WRq_L53AxkjY45eQLafyTCSf0CKE,3986
2
3
  plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
3
4
  plain/debug.py,sha256=abPkJY4aSbBYGEYSZST_ZY3ohXPGDdz9uWQBYRqfd3M,730
@@ -17,21 +18,22 @@ plain/assets/views.py,sha256=T_0Qh6v9qBerEBYbhToigwOzsij-x1z_R-1zETQcIh0,9447
17
18
  plain/chores/README.md,sha256=Da8Nw8ZF7PlAE_iVb0AqJGbLOu0F6HC0cj1K451KBak,1946
18
19
  plain/chores/__init__.py,sha256=r9TXtQCH-VbvfnIJ5F8FxgQC35GRWFOfmMZN3q9niLg,67
19
20
  plain/chores/registry.py,sha256=V3WjuekRI22LFvJbqSkUXQtiOtuE2ZK8gKV1TRvxRUI,1866
20
- plain/cli/README.md,sha256=ompPcgzY2Fdpm579ITmCpFIaZIsiXYbfD61mqkq312M,1860
21
+ plain/cli/README.md,sha256=GzBry6mEilhM80SfVUg02ydGwAk0m-s6FAqQR1nRsMM,2022
21
22
  plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
22
23
  plain/cli/build.py,sha256=dKUYBNegvb6QNckR7XZ7CJJtINwZcmDvbUdv2dWwjf8,3226
23
24
  plain/cli/chores.py,sha256=xXSSFvr8T5jWfLWqe6E8YVMw1BkQxyOHHVuY0x9RH0A,2412
24
- plain/cli/core.py,sha256=sy5otyN5jNOHkcy1EXISB7iMr1itp0rzmBSnEXYGe2k,2866
25
+ plain/cli/core.py,sha256=h8gjeBYQzYTzpmeDMAJTdVDOEoNCNcvnxml-KIsiRPo,2925
25
26
  plain/cli/docs.py,sha256=2CpTv5k-TNWf593tPiglvUVXWBGdfPmbGf8vl5AfJwU,8995
26
27
  plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
27
- plain/cli/preflight.py,sha256=NKyYjcoDjigzfJIDhf7A7degYadaUI05Bw7U9OQ73vs,4170
28
+ plain/cli/help.py,sha256=otRSGxOJ5V8JMjpdZ8XYqUbdlYdJvxOMzQroLOWw-l0,801
29
+ plain/cli/preflight.py,sha256=FWFwMZ0W_t8ObTTRMnBmaiGN8PqdEAWgmSEPGDwZFpA,4148
28
30
  plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
29
31
  plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
30
32
  plain/cli/scaffold.py,sha256=mcywA9DzfwoBSqWl5-Zpgcy1mTNUGEgdvoxXUrGcEVk,1351
31
33
  plain/cli/settings.py,sha256=9cx4bue664I2P7kUedlf4YhCPB0tSKSE4Q8mGyzEv2o,1995
32
34
  plain/cli/shell.py,sha256=iIwvlTdTBjLBBUdXMAmIRWSoynszOZI79-mrBg4RegU,1373
33
35
  plain/cli/startup.py,sha256=wLaFuyUb4ewWhtehBCGicrRCXIIGCRbeCT3ce9hUv-A,1022
34
- plain/cli/urls.py,sha256=CS9NFpwZBWveAR8F3YsoUNySDEK_PwF73oSgLDfkOdI,3776
36
+ plain/cli/urls.py,sha256=7FOvLjfV1GsYKnb7SGlIgEfchQcrkWdYU1nY6aazGBI,3855
35
37
  plain/cli/utils.py,sha256=VwlIh0z7XxzVV8I3qM2kZo07fkJFPoeeVZa1ODG616k,258
36
38
  plain/csrf/README.md,sha256=nxCpPk1HF5eAM-7paxg9D-9RVCU9jXsSPAVHkJvA_DU,717
37
39
  plain/csrf/middleware.py,sha256=U3B9R7ciO_UAf7O3jdNtVu6QZ_3Yrm8isRdnW6bVKX4,17401
@@ -78,10 +80,10 @@ plain/preflight/README.md,sha256=9fE0Ql2JNd3CI420eg1sP_xmaim-6Ejlzi_hfk3tVS0,151
78
80
  plain/preflight/__init__.py,sha256=j4-yPnrM5hmjumrdkBLOQjFHzRHpA6wCjiFpMNBjIqY,619
79
81
  plain/preflight/files.py,sha256=D_pBSwRXpXy2-3FWywweozuxrhIaR8w5hpPA2d6XMPs,522
80
82
  plain/preflight/messages.py,sha256=B6VyXzu7HTJHaPVK4G1L_1HVHG87CT7JPtcDk8QYSeE,2322
81
- plain/preflight/registry.py,sha256=wHLq6LSMkKunkxElBmNwzMzQx3tc6OGYeKyOOi0tuyQ,2333
83
+ plain/preflight/registry.py,sha256=vcqzaE1MIneNL_ydKPy_1zrSThnzsrWARSClLCJ-4b8,2331
82
84
  plain/preflight/security.py,sha256=oxUZBp2M0bpBfUoLYepIxoex2Y90nyjlrL8XU8UTHYY,2438
83
85
  plain/preflight/urls.py,sha256=cQ-WnFa_5oztpKdtwhuIGb7pXEml__bHsjs1SWO2YNI,1468
84
- plain/runtime/README.md,sha256=yPcrJKnFEqkrb0KlhLZi2VInV-LCl6ENOZs7mOHlhi0,4753
86
+ plain/runtime/README.md,sha256=1h30eRTg5-lY-G7GF_4TdUiUcOLVzKR15mSrmyeU8eI,4584
85
87
  plain/runtime/__init__.py,sha256=8GtvKROf3HUCtneDYXTbEioPcCtwnV76dP57n2PnjuE,2343
86
88
  plain/runtime/global_settings.py,sha256=MpJ6vtBMO2EYALaKXrtgcGoxLh9Y549zwwErC5D5K7I,5343
87
89
  plain/runtime/user_settings.py,sha256=OzMiEkE6ZQ50nxd1WIqirXPiNuMAQULklYHEzgzLWgA,11027
@@ -100,7 +102,7 @@ plain/templates/jinja/filters.py,sha256=t_u8BkWtEpJFLbLywONxWKrenSzOvDJhfOLwlZiX
100
102
  plain/templates/jinja/globals.py,sha256=VMpuMZvwWOmb5MbzKK-ox-QEX_WSsXFxq0mm8biJgaU,558
101
103
  plain/test/README.md,sha256=fv4YzziU2QxgcNHSgv7aDUO45sDOofVuCNrV1NPbWzo,1106
102
104
  plain/test/__init__.py,sha256=MhNHtp7MYBl9kq-pMRGY11kJ6kU1I6vOkjNkit1TYRg,94
103
- plain/test/client.py,sha256=lqzOf8EJo_u54dW_oiPy0m5mW-Nt3-CWyrBh9_MJnms,26930
105
+ plain/test/client.py,sha256=XjLjkeDl1rQRaFoeiCvvD5GXfxIxpuic2OnxjoX05tA,25573
104
106
  plain/test/encoding.py,sha256=YJBOIE-BQRA5yl4zHnQy-9l67mJDTFmfy1DQXK0Wk-Q,3199
105
107
  plain/test/exceptions.py,sha256=b-GHicg87Gh73W3g8QGWuSHi9PrQFVsxgWvEXDLt8gQ,290
106
108
  plain/urls/README.md,sha256=ijFGmrkUY9buBqO_i1GZaN3V55vl9xwEADtHOx_ZHPY,3724
@@ -145,8 +147,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
145
147
  plain/views/objects.py,sha256=GGbcfg_9fPZ-PiaBwIHG2e__8GfWDR7JQtQ15wTyiHg,5970
146
148
  plain/views/redirect.py,sha256=daq2cQIkdDF78bt43sjuZxRAyJm_t_SKw6tyPmiXPIc,1985
147
149
  plain/views/templates.py,sha256=ivkI7LU7BXDQ0d4Geab96Is4-Cp03KbIntXRT1J8e6I,2139
148
- plain-0.48.0.dist-info/METADATA,sha256=N4Mvv-2mriZwcXfYBEWMz88pp3Sc4oik88GXI4akoMk,4297
149
- plain-0.48.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
150
- plain-0.48.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
151
- plain-0.48.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
152
- plain-0.48.0.dist-info/RECORD,,
150
+ plain-0.50.0.dist-info/METADATA,sha256=GLcRDpS06XPtx52qLG5bIfrROE3g8CFffLNSz6Mdxt8,4297
151
+ plain-0.50.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
152
+ plain-0.50.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
153
+ plain-0.50.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
154
+ plain-0.50.0.dist-info/RECORD,,
File without changes