plain.auth 0.20.1__py3-none-any.whl → 0.20.3__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/auth/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # plain-auth changelog
2
2
 
3
+ ## [0.20.3](https://github.com/dropseed/plain/releases/plain-auth@0.20.3) (2025-10-07)
4
+
5
+ ### What's changed
6
+
7
+ - Updated 0.20.0 changelog with additional upgrade instructions about `login_required` default ([221591e](https://github.com/dropseed/plain/commit/221591e48a))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required
12
+
13
+ ## [0.20.2](https://github.com/dropseed/plain/releases/plain-auth@0.20.2) (2025-10-06)
14
+
15
+ ### What's changed
16
+
17
+ - Added comprehensive type annotations across the entire package for improved IDE support and type checking ([786c1db](https://github.com/dropseed/plain/commit/786c1dbdbd))
18
+
19
+ ### Upgrade instructions
20
+
21
+ - No changes required
22
+
3
23
  ## [0.20.1](https://github.com/dropseed/plain/releases/plain-auth@0.20.1) (2025-10-02)
4
24
 
5
25
  ### What's changed
@@ -23,6 +43,7 @@
23
43
 
24
44
  - Remove `plain.auth.middleware.AuthenticationMiddleware` from your `MIDDLEWARE` setting
25
45
  - In views, use `AuthViewMixin` for access to `self.user` instead of `self.request.user`
46
+ - The default for `login_required` in `AuthViewMixin` is now `False`, so explicitly set `login_required = True` if needed
26
47
  - Replace `request.user` with `get_request_user(request)` in code outside of `AuthViewMixin` views
27
48
  - In templates, replace `{{ request.user }}` with `{{ user }}` (from `AuthViewMixin`) or with `{{ get_current_user() }}`
28
49
 
plain/auth/requests.py CHANGED
@@ -1,24 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Any
4
4
  from weakref import WeakKeyDictionary
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from plain.http import Request
8
8
 
9
- from .sessions import get_user_model
9
+ _request_users: WeakKeyDictionary[Request, Any | None] = WeakKeyDictionary()
10
10
 
11
- User = get_user_model()
12
11
 
13
- _request_users: WeakKeyDictionary[Request, User | None] = WeakKeyDictionary()
14
-
15
-
16
- def set_request_user(request: Request, user: User | None) -> None:
12
+ def set_request_user(request: Request, user: Any | None) -> None:
17
13
  """Store the authenticated user for this request."""
18
14
  _request_users[request] = user
19
15
 
20
16
 
21
- def get_request_user(request: Request) -> User | None:
17
+ def get_request_user(request: Request) -> Any | None:
22
18
  """
23
19
  Get the authenticated user for this request, if any.
24
20
 
plain/auth/sessions.py CHANGED
@@ -1,4 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import hmac
4
+ from collections.abc import Generator
5
+ from typing import TYPE_CHECKING, Any
2
6
 
3
7
  from plain.exceptions import ImproperlyConfigured
4
8
  from plain.models import models_registry
@@ -9,18 +13,21 @@ from plain.utils.encoding import force_bytes
9
13
 
10
14
  from .requests import get_request_user, set_request_user
11
15
 
16
+ if TYPE_CHECKING:
17
+ from plain.http import Request
18
+
12
19
  USER_ID_SESSION_KEY = "_auth_user_id"
13
20
  USER_HASH_SESSION_KEY = "_auth_user_hash"
14
21
 
15
22
 
16
- def get_session_auth_hash(user):
23
+ def get_session_auth_hash(user: Any) -> str:
17
24
  """
18
25
  Return an HMAC of the password field.
19
26
  """
20
27
  return _get_session_auth_hash(user)
21
28
 
22
29
 
23
- def update_session_auth_hash(request, user):
30
+ def update_session_auth_hash(request: Request, user: Any) -> None:
24
31
  """
25
32
  Updating a user's password (for example) logs out all sessions for the user.
26
33
 
@@ -36,12 +43,12 @@ def update_session_auth_hash(request, user):
36
43
  session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
37
44
 
38
45
 
39
- def get_session_auth_fallback_hash(user):
46
+ def get_session_auth_fallback_hash(user: Any) -> Generator[str, None, None]:
40
47
  for fallback_secret in settings.SECRET_KEY_FALLBACKS:
41
48
  yield _get_session_auth_hash(user, secret=fallback_secret)
42
49
 
43
50
 
44
- def _get_session_auth_hash(user, secret=None):
51
+ def _get_session_auth_hash(user: Any, secret: str | None = None) -> str:
45
52
  key_salt = "plain.auth.get_session_auth_hash"
46
53
  return salted_hmac(
47
54
  key_salt,
@@ -51,7 +58,7 @@ def _get_session_auth_hash(user, secret=None):
51
58
  ).hexdigest()
52
59
 
53
60
 
54
- def login(request, user):
61
+ def login(request: Request, user: Any) -> None:
55
62
  """
56
63
  Persist a user id and a backend in the request. This way a user doesn't
57
64
  have to reauthenticate on every request. Note that data set during
@@ -87,7 +94,7 @@ def login(request, user):
87
94
  set_request_user(request, user)
88
95
 
89
96
 
90
- def logout(request):
97
+ def logout(request: Request) -> None:
91
98
  """
92
99
  Remove the authenticated user's ID from the request and flush their session
93
100
  data.
@@ -99,7 +106,7 @@ def logout(request):
99
106
  set_request_user(request, None)
100
107
 
101
108
 
102
- def get_user_model():
109
+ def get_user_model() -> type[Any]:
103
110
  """
104
111
  Return the User model that is active in this project.
105
112
  """
@@ -115,7 +122,7 @@ def get_user_model():
115
122
  )
116
123
 
117
124
 
118
- def get_user(request):
125
+ def get_user(request: Request) -> Any | None:
119
126
  """
120
127
  Return the user model instance associated with the given request session.
121
128
  If no user is retrieved, return None.
plain/auth/templates.py CHANGED
@@ -1,13 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
1
5
  from jinja2 import pass_context
2
6
 
3
7
  from plain.templates import register_template_global
4
8
 
5
9
  from .requests import get_request_user
6
10
 
11
+ if TYPE_CHECKING:
12
+ from jinja2.runtime import Context
13
+
7
14
 
8
15
  @register_template_global
9
16
  @pass_context
10
- def get_current_user(context):
17
+ def get_current_user(context: Context) -> Any | None:
11
18
  """Get the authenticated user for the current request."""
12
19
  request = context.get("request")
13
20
  assert request is not None, "No request in template context"
plain/auth/test.py CHANGED
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from http.cookies import SimpleCookie
4
+ from typing import TYPE_CHECKING, Any
2
5
 
3
6
  from plain.http.request import Request
4
7
  from plain.runtime import settings
@@ -8,8 +11,11 @@ from plain.sessions.requests import get_request_session, set_request_session
8
11
  from .requests import set_request_user
9
12
  from .sessions import get_user, login, logout
10
13
 
14
+ if TYPE_CHECKING:
15
+ from plain.test.client import Client
16
+
11
17
 
12
- def login_client(client, user):
18
+ def login_client(client: Client, user: Any) -> None:
13
19
  """Log a user into a test client."""
14
20
  request = Request()
15
21
  if client.session:
@@ -32,7 +38,7 @@ def login_client(client, user):
32
38
  client.cookies[session_cookie].update(cookie_data)
33
39
 
34
40
 
35
- def logout_client(client):
41
+ def logout_client(client: Client) -> None:
36
42
  """Log out a user from a test client."""
37
43
  request = Request()
38
44
  if client.session:
plain/auth/utils.py CHANGED
@@ -1,8 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
1
5
  from plain.urls import NoReverseMatch, reverse
2
6
  from plain.utils.functional import Promise
3
7
 
4
8
 
5
- def resolve_url(to, *args, **kwargs):
9
+ def resolve_url(to: Any, *args: Any, **kwargs: Any) -> str:
6
10
  """
7
11
  Return a URL appropriate for the arguments passed.
8
12
 
plain/auth/views.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from functools import cached_property
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any
5
5
  from urllib.parse import urlparse, urlunparse
6
6
 
7
7
  from plain.exceptions import PermissionDenied
@@ -23,13 +23,9 @@ from .utils import resolve_url
23
23
  if TYPE_CHECKING:
24
24
  from plain.http import Request
25
25
 
26
- from .sessions import get_user_model
27
-
28
- User = get_user_model()
29
-
30
26
 
31
27
  class LoginRequired(Exception):
32
- def __init__(self, login_url=None, redirect_field_name="next"):
28
+ def __init__(self, login_url: str | None = None, redirect_field_name: str = "next"):
33
29
  self.login_url = login_url or settings.AUTH_LOGIN_URL
34
30
  self.redirect_field_name = redirect_field_name
35
31
 
@@ -42,7 +38,7 @@ class AuthViewMixin(SessionViewMixin):
42
38
  request: Request
43
39
 
44
40
  @cached_property
45
- def user(self) -> User | None:
41
+ def user(self) -> Any | None:
46
42
  """Get the authenticated user for this request."""
47
43
  from .requests import get_request_user
48
44
 
@@ -116,12 +112,14 @@ class AuthViewMixin(SessionViewMixin):
116
112
 
117
113
 
118
114
  class LogoutView(View):
119
- def post(self):
115
+ def post(self) -> ResponseRedirect:
120
116
  logout(self.request)
121
117
  return ResponseRedirect("/")
122
118
 
123
119
 
124
- def redirect_to_login(next, login_url=None, redirect_field_name="next"):
120
+ def redirect_to_login(
121
+ next: str, login_url: str | None = None, redirect_field_name: str = "next"
122
+ ) -> ResponseRedirect:
125
123
  """
126
124
  Redirect the user to the login page, passing the given 'next' page.
127
125
  """
@@ -133,4 +131,4 @@ def redirect_to_login(next, login_url=None, redirect_field_name="next"):
133
131
  querystring[redirect_field_name] = next
134
132
  login_url_parts[4] = querystring.urlencode(safe="/")
135
133
 
136
- return ResponseRedirect(urlunparse(login_url_parts))
134
+ return ResponseRedirect(str(urlunparse(login_url_parts)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.auth
3
- Version: 0.20.1
3
+ Version: 0.20.3
4
4
  Summary: Add users to your app and decide what they can access.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -0,0 +1,14 @@
1
+ plain/auth/CHANGELOG.md,sha256=C0gBGYr2hupTa7mjegRf-sEJahDLuBQZOmSC50jztUY,5304
2
+ plain/auth/README.md,sha256=I1SeOyrBnF0GAjD7T0k5OlZ2bSHz9---nElinaobewc,4030
3
+ plain/auth/__init__.py,sha256=CrOsS74CPGN1nPTTfie13mPgdyVLRyZ1YwDPIA77uaA,179
4
+ plain/auth/default_settings.py,sha256=65VzDn3j61OMn78Lg6Zuds4A8QKzJJ_0G9KoFqAOIRo,466
5
+ plain/auth/requests.py,sha256=jUlMTOWFt0qfDF_uVkOqaP9rZiCLrAofsmirCwyRGEE,880
6
+ plain/auth/sessions.py,sha256=xDp1EiB0cV5w3L5XioqO8vs77wnF8cvreExms3-e744,6015
7
+ plain/auth/templates.py,sha256=bOMPfLrf8SSxOSWrKTkqKGaK3aNBTLxe62HJulghutU,556
8
+ plain/auth/test.py,sha256=SHawhwarJEMVfaLkjpiuFVSWZoGIMwhzXreU_T1zvCE,1599
9
+ plain/auth/utils.py,sha256=9kKWh1QqxA8Esct-jBvTCdjBYOHpO_Tg1YeV9WxYmxg,1362
10
+ plain/auth/views.py,sha256=vWLMeykTrA5LtA179uE7w-BY9Kv25r1VScLHooDvZUA,4792
11
+ plain_auth-0.20.3.dist-info/METADATA,sha256=OUbZL8QidYcsulh1gkbQYXtghQtooqusk-n9rZkc7yg,4390
12
+ plain_auth-0.20.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
+ plain_auth-0.20.3.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
14
+ plain_auth-0.20.3.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- plain/auth/CHANGELOG.md,sha256=W74wFZKot-9heq8l_MsoLSDmHLdAe5LSo9qFPliXFS0,4533
2
- plain/auth/README.md,sha256=I1SeOyrBnF0GAjD7T0k5OlZ2bSHz9---nElinaobewc,4030
3
- plain/auth/__init__.py,sha256=CrOsS74CPGN1nPTTfie13mPgdyVLRyZ1YwDPIA77uaA,179
4
- plain/auth/default_settings.py,sha256=65VzDn3j61OMn78Lg6Zuds4A8QKzJJ_0G9KoFqAOIRo,466
5
- plain/auth/requests.py,sha256=f8QUpqHueoTYnL2ITgRXxLFYLSxra9jW3p9pjFgj0B0,949
6
- plain/auth/sessions.py,sha256=Pz27K2wFfij0H0nGg_2rq0WJpcgTWW22bG0YvHM-dfc,5679
7
- plain/auth/templates.py,sha256=MQ9vdxE2fBXN-F043g8EGm93s6HRvEMtGHrFcbAr3Fs,400
8
- plain/auth/test.py,sha256=bLxQEp1mM4t9b-bSSq2u9_Z07Wkl--7U_mSBb9VV7BU,1428
9
- plain/auth/utils.py,sha256=eEON0Mo928l-aW5tqBuoTdVke8aP4majxVtAFoLroSE,1280
10
- plain/auth/views.py,sha256=Gm91sB_Z47h-8SwxCMKVKl57FSjed_AM5fdeaiPmz-E,4761
11
- plain_auth-0.20.1.dist-info/METADATA,sha256=5E0uGbf7uFA-F0ywFO45xQOLQSCbvYw9nSNVyezRITI,4390
12
- plain_auth-0.20.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
- plain_auth-0.20.1.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
14
- plain_auth-0.20.1.dist-info/RECORD,,