python3-commons 0.17.4__tar.gz → 0.17.6__tar.gz
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.
- {python3_commons-0.17.4/src/python3_commons.egg-info → python3_commons-0.17.6}/PKG-INFO +3 -1
- {python3_commons-0.17.4 → python3_commons-0.17.6}/pyproject.toml +3 -1
- python3_commons-0.17.6/src/python3_commons/auth.py +178 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/conf.py +6 -5
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/object_storage.py +4 -4
- {python3_commons-0.17.4 → python3_commons-0.17.6/src/python3_commons.egg-info}/PKG-INFO +3 -1
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/requires.txt +2 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/uv.lock +43 -32
- python3_commons-0.17.4/src/python3_commons/auth.py +0 -79
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.coveragerc +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/Dockerfile +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/devcontainer.json +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/docker-compose.yml +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.env_template +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/checks.yml +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/release-on-tag-push.yml +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.gitignore +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.pre-commit-config.yaml +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/.python-version +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/AUTHORS.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/CHANGELOG.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/LICENSE +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/README.md +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/README.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/Makefile +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/_static/.gitignore +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/authors.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/changelog.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/conf.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/index.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/license.rst +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/setup.cfg +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/async_functools.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/audit.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/cache.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/users.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/exceptions.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/generators.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/helpers.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/common.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/SOURCES.txt +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/test_cache.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/test_osc.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/conftest.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/log/__init__.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/log/test_formatters.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_async_functools.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_audit.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_helpers.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_msgpack.py +0 -0
- {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_msgspec.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.6
|
|
4
4
|
Summary: Re-usable Python3 code
|
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0
|
|
@@ -28,8 +28,10 @@ Requires-Dist: zeep~=4.3.2; extra == "audit"
|
|
|
28
28
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
29
29
|
Provides-Extra: authn
|
|
30
30
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
31
|
+
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
31
32
|
Provides-Extra: authz
|
|
32
33
|
Requires-Dist: python3_commons[database]; extra == "authz"
|
|
34
|
+
Requires-Dist: python3_commons[api-client]; extra == "authz"
|
|
33
35
|
Provides-Extra: cache
|
|
34
36
|
Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
|
|
35
37
|
Provides-Extra: database
|
|
@@ -40,9 +40,11 @@ audit = [
|
|
|
40
40
|
]
|
|
41
41
|
authn = [
|
|
42
42
|
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
43
|
+
"python3_commons[api-client]"
|
|
43
44
|
]
|
|
44
45
|
authz = [
|
|
45
|
-
"python3_commons[database]"
|
|
46
|
+
"python3_commons[database]",
|
|
47
|
+
"python3_commons[api-client]"
|
|
46
48
|
]
|
|
47
49
|
cache = [
|
|
48
50
|
"valkey[libvalkey]~=6.1.1"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
from typing import Self, TypeVar
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import aiohttp
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
msg = 'Install python3-commons[authn] to use this feature'
|
|
12
|
+
raise RuntimeError(msg) from e
|
|
13
|
+
|
|
14
|
+
import msgspec
|
|
15
|
+
|
|
16
|
+
from python3_commons.conf import oidc_settings
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TokenData(msgspec.Struct):
|
|
22
|
+
exp: int
|
|
23
|
+
iat: int
|
|
24
|
+
iss: str
|
|
25
|
+
sub: str
|
|
26
|
+
aud: str | Sequence[str] | None = None
|
|
27
|
+
email: str | None = None
|
|
28
|
+
name: str | None = None
|
|
29
|
+
preferred_username: str | None = None
|
|
30
|
+
realm_access: dict[str, Sequence[str]] | None = None
|
|
31
|
+
resource_access: dict[str, dict[str, Sequence[str]]] | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def roles(self) -> list[str]:
|
|
35
|
+
roles_list = []
|
|
36
|
+
|
|
37
|
+
if self.realm_access:
|
|
38
|
+
roles_list.extend(self.realm_access.get('roles', []))
|
|
39
|
+
|
|
40
|
+
if self.resource_access:
|
|
41
|
+
for client in self.resource_access.values():
|
|
42
|
+
roles_list.extend(client.get('roles', []))
|
|
43
|
+
|
|
44
|
+
return list(set(roles_list))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
T = TypeVar('T', bound=TokenData)
|
|
48
|
+
OIDC_CONFIG_URL = (
|
|
49
|
+
f'{oidc_settings.authority_internal_url or oidc_settings.authority_url}/.well-known/openid-configuration'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OIDCTokenResponse(msgspec.Struct):
|
|
54
|
+
access_token: str
|
|
55
|
+
token_type: str
|
|
56
|
+
expires_in: int
|
|
57
|
+
refresh_token: str
|
|
58
|
+
scope: str
|
|
59
|
+
id_token: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def fetch_openid_config() -> dict:
|
|
63
|
+
"""
|
|
64
|
+
Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
|
|
65
|
+
"""
|
|
66
|
+
async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
|
|
67
|
+
if response.status != HTTPStatus.OK:
|
|
68
|
+
_msg = 'Failed to fetch OpenID configuration'
|
|
69
|
+
|
|
70
|
+
raise RuntimeError(_msg)
|
|
71
|
+
|
|
72
|
+
return await response.json()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def fetch_jwks(jwks_uri: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
|
|
78
|
+
"""
|
|
79
|
+
if oidc_settings.authority_internal_url:
|
|
80
|
+
jwks_uri = jwks_uri.replace(str(oidc_settings.authority_url), str(oidc_settings.authority_internal_url))
|
|
81
|
+
|
|
82
|
+
async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
|
|
83
|
+
if response.status != HTTPStatus.OK:
|
|
84
|
+
_msg = 'Failed to fetch JWKS'
|
|
85
|
+
|
|
86
|
+
raise RuntimeError(_msg)
|
|
87
|
+
|
|
88
|
+
return await response.json()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class OIDCError(Exception):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class OIDCAuthError(OIDCError):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class OIDCClient:
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
token_url: str,
|
|
103
|
+
client_id: str,
|
|
104
|
+
client_secret: str | None = None,
|
|
105
|
+
*,
|
|
106
|
+
timeout: float = 10.0,
|
|
107
|
+
session: aiohttp.ClientSession | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
self._token_url = token_url
|
|
110
|
+
self._client_id = client_id
|
|
111
|
+
self._client_secret = client_secret
|
|
112
|
+
self._timeout = aiohttp.ClientTimeout(total=timeout)
|
|
113
|
+
self._session = session
|
|
114
|
+
self._owns_session = session is None
|
|
115
|
+
|
|
116
|
+
async def __aenter__(self) -> Self:
|
|
117
|
+
if self._session is None:
|
|
118
|
+
self._session = aiohttp.ClientSession(timeout=self._timeout)
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
async def __aexit__(self, *_: object) -> None:
|
|
122
|
+
if self._owns_session and self._session:
|
|
123
|
+
await self._session.close()
|
|
124
|
+
|
|
125
|
+
async def fetch_token(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
username: str,
|
|
129
|
+
password: str,
|
|
130
|
+
scope: str = 'openid profile email',
|
|
131
|
+
) -> OIDCTokenResponse:
|
|
132
|
+
if self._session is None:
|
|
133
|
+
msg = 'ClientSession not initialized'
|
|
134
|
+
|
|
135
|
+
raise RuntimeError(msg)
|
|
136
|
+
|
|
137
|
+
data = {
|
|
138
|
+
'grant_type': 'password',
|
|
139
|
+
'username': username,
|
|
140
|
+
'password': password,
|
|
141
|
+
'client_id': self._client_id,
|
|
142
|
+
'scope': scope,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if self._client_secret:
|
|
146
|
+
data['client_secret'] = self._client_secret
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
async with self._session.post(
|
|
150
|
+
self._token_url,
|
|
151
|
+
data=data,
|
|
152
|
+
headers={'Content-Type': 'application/x-www-form-urlencoded'},
|
|
153
|
+
) as resp:
|
|
154
|
+
payload = await resp.json(content_type=None)
|
|
155
|
+
|
|
156
|
+
except TimeoutError as e:
|
|
157
|
+
msg = 'OIDC request timed out'
|
|
158
|
+
|
|
159
|
+
raise OIDCError(msg) from e
|
|
160
|
+
except aiohttp.ClientError as e:
|
|
161
|
+
msg = 'OIDC transport error'
|
|
162
|
+
|
|
163
|
+
raise OIDCError(msg) from e
|
|
164
|
+
|
|
165
|
+
if not resp.ok:
|
|
166
|
+
error = payload.get('error')
|
|
167
|
+
description = payload.get('error_description')
|
|
168
|
+
|
|
169
|
+
if error in {'invalid_grant', 'invalid_client'}:
|
|
170
|
+
msg = f'{error}: {description}'
|
|
171
|
+
|
|
172
|
+
raise OIDCAuthError(msg)
|
|
173
|
+
|
|
174
|
+
msg = f'{error}: {description}'
|
|
175
|
+
|
|
176
|
+
raise OIDCError(msg)
|
|
177
|
+
|
|
178
|
+
return payload
|
|
@@ -21,6 +21,7 @@ class OIDCSettings(BaseSettings):
|
|
|
21
21
|
authority_url: HttpUrl | None = None
|
|
22
22
|
authority_internal_url: HttpUrl | None = None
|
|
23
23
|
client_id: str | None = None
|
|
24
|
+
client_secret: SecretStr = SecretStr('')
|
|
24
25
|
redirect_uri: str | None = None
|
|
25
26
|
scope: StringSeq = (
|
|
26
27
|
'openid',
|
|
@@ -58,11 +59,11 @@ class DBSettings(BaseSettings):
|
|
|
58
59
|
@model_validator(mode='after')
|
|
59
60
|
def build_dsn_if_missing(self) -> DBSettings:
|
|
60
61
|
if self.dsn is None and all(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
(
|
|
63
|
+
self.user,
|
|
64
|
+
self.password,
|
|
65
|
+
self.name,
|
|
66
|
+
)
|
|
66
67
|
):
|
|
67
68
|
self.dsn = PostgresDsn.build(
|
|
68
69
|
scheme=self.scheme,
|
|
@@ -140,7 +140,7 @@ async def list_objects(bucket_name: str, prefix: str, *, recursive: bool = True)
|
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
async def get_object_streams(
|
|
143
|
-
|
|
143
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
144
144
|
) -> AsyncGenerator[tuple[str, datetime, StreamingBody]]:
|
|
145
145
|
async for obj in list_objects(bucket_name, path, recursive=recursive):
|
|
146
146
|
object_name = obj['Key']
|
|
@@ -151,7 +151,7 @@ async def get_object_streams(
|
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
async def get_objects(
|
|
154
|
-
|
|
154
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
155
155
|
) -> AsyncGenerator[tuple[str, datetime, bytes]]:
|
|
156
156
|
async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive=recursive):
|
|
157
157
|
data = await stream.read()
|
|
@@ -173,7 +173,7 @@ async def remove_object(bucket_name: str, object_name: str) -> None:
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
async def remove_objects(
|
|
176
|
-
|
|
176
|
+
bucket_name: str, prefix: str | None = None, object_names: Iterable[str] | None = None
|
|
177
177
|
) -> Sequence[Mapping] | None:
|
|
178
178
|
storage = ObjectStorage(s3_settings)
|
|
179
179
|
|
|
@@ -196,7 +196,7 @@ async def remove_objects(
|
|
|
196
196
|
chunk_size = 1000
|
|
197
197
|
|
|
198
198
|
for i in range(0, len(objects_to_delete), chunk_size):
|
|
199
|
-
chunk = objects_to_delete[i: i + chunk_size]
|
|
199
|
+
chunk = objects_to_delete[i : i + chunk_size]
|
|
200
200
|
|
|
201
201
|
response = await s3_client.delete_objects(Bucket=bucket_name, Delete={'Objects': chunk})
|
|
202
202
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.6
|
|
4
4
|
Summary: Re-usable Python3 code
|
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0
|
|
@@ -28,8 +28,10 @@ Requires-Dist: zeep~=4.3.2; extra == "audit"
|
|
|
28
28
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
29
29
|
Provides-Extra: authn
|
|
30
30
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
31
|
+
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
31
32
|
Provides-Extra: authz
|
|
32
33
|
Requires-Dist: python3_commons[database]; extra == "authz"
|
|
34
|
+
Requires-Dist: python3_commons[api-client]; extra == "authz"
|
|
33
35
|
Provides-Extra: cache
|
|
34
36
|
Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
|
|
35
37
|
Provides-Extra: database
|
|
@@ -229,16 +229,16 @@ wheels = [
|
|
|
229
229
|
|
|
230
230
|
[[package]]
|
|
231
231
|
name = "build"
|
|
232
|
-
version = "1.
|
|
232
|
+
version = "1.5.0"
|
|
233
233
|
source = { registry = "https://pypi.org/simple" }
|
|
234
234
|
dependencies = [
|
|
235
235
|
{ name = "colorama", marker = "os_name == 'nt'" },
|
|
236
236
|
{ name = "packaging" },
|
|
237
237
|
{ name = "pyproject-hooks" },
|
|
238
238
|
]
|
|
239
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
239
|
+
sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" }
|
|
240
240
|
wheels = [
|
|
241
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" },
|
|
242
242
|
]
|
|
243
243
|
|
|
244
244
|
[[package]]
|
|
@@ -442,29 +442,29 @@ wheels = [
|
|
|
442
442
|
|
|
443
443
|
[[package]]
|
|
444
444
|
name = "greenlet"
|
|
445
|
-
version = "3.
|
|
446
|
-
source = { registry = "https://pypi.org/simple" }
|
|
447
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
448
|
-
wheels = [
|
|
449
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
450
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
451
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
452
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
453
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
454
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
455
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
456
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
457
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
458
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
459
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
460
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
461
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
462
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
463
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
464
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
465
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
466
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
467
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
445
|
+
version = "3.5.0"
|
|
446
|
+
source = { registry = "https://pypi.org/simple" }
|
|
447
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" }
|
|
448
|
+
wheels = [
|
|
449
|
+
{ url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" },
|
|
450
|
+
{ url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" },
|
|
451
|
+
{ url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" },
|
|
452
|
+
{ url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086, upload-time = "2026-04-27T13:02:42.314Z" },
|
|
453
|
+
{ url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" },
|
|
454
|
+
{ url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799, upload-time = "2026-04-27T13:05:17.094Z" },
|
|
455
|
+
{ url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" },
|
|
456
|
+
{ url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" },
|
|
457
|
+
{ url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" },
|
|
458
|
+
{ url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" },
|
|
459
|
+
{ url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" },
|
|
460
|
+
{ url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" },
|
|
461
|
+
{ url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" },
|
|
462
|
+
{ url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015, upload-time = "2026-04-27T13:02:43.973Z" },
|
|
463
|
+
{ url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" },
|
|
464
|
+
{ url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275, upload-time = "2026-04-27T13:05:18.28Z" },
|
|
465
|
+
{ url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" },
|
|
466
|
+
{ url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" },
|
|
467
|
+
{ url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" },
|
|
468
468
|
]
|
|
469
469
|
|
|
470
470
|
[[package]]
|
|
@@ -688,11 +688,11 @@ wheels = [
|
|
|
688
688
|
|
|
689
689
|
[[package]]
|
|
690
690
|
name = "pip"
|
|
691
|
-
version = "26.
|
|
691
|
+
version = "26.1"
|
|
692
692
|
source = { registry = "https://pypi.org/simple" }
|
|
693
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
693
|
+
sdist = { url = "https://files.pythonhosted.org/packages/73/7e/d2b04004e1068ad4fdfa2f227b839b5d03e602e47cdbbf49de71137c9546/pip-26.1.tar.gz", hash = "sha256:81e13ebcca3ffa8cc85e4deff5c27e1ee26dea0aa7fc2f294a073ac208806ff3", size = 1840316, upload-time = "2026-04-26T21:00:05.406Z" }
|
|
694
694
|
wheels = [
|
|
695
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
695
|
+
{ url = "https://files.pythonhosted.org/packages/70/7a/be4bd8bcbb24ea475856dd68159d78b03b2bb53dae369f69c9606b8888f5/pip-26.1-py3-none-any.whl", hash = "sha256:4e8486d821d814b77319acb7b9e8bf5a4ee7590a643e7cb21029f209be8573c1", size = 1812804, upload-time = "2026-04-26T21:00:03.194Z" },
|
|
696
696
|
]
|
|
697
697
|
|
|
698
698
|
[[package]]
|
|
@@ -1022,11 +1022,20 @@ audit = [
|
|
|
1022
1022
|
{ name = "zeep" },
|
|
1023
1023
|
]
|
|
1024
1024
|
authn = [
|
|
1025
|
+
{ name = "aiobotocore" },
|
|
1025
1026
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1027
|
+
{ name = "lxml" },
|
|
1028
|
+
{ name = "object-storage-client" },
|
|
1029
|
+
{ name = "zeep" },
|
|
1026
1030
|
]
|
|
1027
1031
|
authz = [
|
|
1032
|
+
{ name = "aiobotocore" },
|
|
1033
|
+
{ name = "aiohttp", extra = ["speedups"] },
|
|
1028
1034
|
{ name = "asyncpg" },
|
|
1035
|
+
{ name = "lxml" },
|
|
1036
|
+
{ name = "object-storage-client" },
|
|
1029
1037
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
|
1038
|
+
{ name = "zeep" },
|
|
1030
1039
|
]
|
|
1031
1040
|
cache = [
|
|
1032
1041
|
{ name = "valkey", extra = ["libvalkey"] },
|
|
@@ -1069,6 +1078,8 @@ requires-dist = [
|
|
|
1069
1078
|
{ name = "msgspec", specifier = "==0.21.1" },
|
|
1070
1079
|
{ name = "object-storage-client", marker = "extra == 'object-storage'", specifier = "==0.0.23" },
|
|
1071
1080
|
{ name = "pydantic-settings", specifier = "~=2.14.0" },
|
|
1081
|
+
{ name = "python3-commons", extras = ["api-client"], marker = "extra == 'authn'" },
|
|
1082
|
+
{ name = "python3-commons", extras = ["api-client"], marker = "extra == 'authz'" },
|
|
1072
1083
|
{ name = "python3-commons", extras = ["api-client", "audit", "authn", "authz", "cache", "database", "object-storage"], marker = "extra == 'all'" },
|
|
1073
1084
|
{ name = "python3-commons", extras = ["database"], marker = "extra == 'authz'" },
|
|
1074
1085
|
{ name = "python3-commons", extras = ["object-storage"], marker = "extra == 'api-client'" },
|
|
@@ -1327,7 +1338,7 @@ wheels = [
|
|
|
1327
1338
|
|
|
1328
1339
|
[[package]]
|
|
1329
1340
|
name = "virtualenv"
|
|
1330
|
-
version = "21.
|
|
1341
|
+
version = "21.3.0"
|
|
1331
1342
|
source = { registry = "https://pypi.org/simple" }
|
|
1332
1343
|
dependencies = [
|
|
1333
1344
|
{ name = "distlib" },
|
|
@@ -1335,9 +1346,9 @@ dependencies = [
|
|
|
1335
1346
|
{ name = "platformdirs" },
|
|
1336
1347
|
{ name = "python-discovery" },
|
|
1337
1348
|
]
|
|
1338
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1349
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/6331f7a7fe70131c301106ec1e7cf23e2501bf7d4ca3636805801ca191bb/virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e", size = 7614069, upload-time = "2026-04-27T17:05:58.927Z" }
|
|
1339
1350
|
wheels = [
|
|
1340
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1351
|
+
{ url = "https://files.pythonhosted.org/packages/4b/eb/03bfb1299d4c4510329e470f13f9a4ce793df7fcb5a2fd3510f911066f61/virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7", size = 7594690, upload-time = "2026-04-27T17:05:55.468Z" },
|
|
1341
1352
|
]
|
|
1342
1353
|
|
|
1343
1354
|
[[package]]
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from collections.abc import Sequence
|
|
5
|
-
from http import HTTPStatus
|
|
6
|
-
from typing import TypeVar
|
|
7
|
-
|
|
8
|
-
try:
|
|
9
|
-
import aiohttp
|
|
10
|
-
except ImportError as e:
|
|
11
|
-
msg = 'Install python3-commons[authn] to use this feature'
|
|
12
|
-
raise RuntimeError(msg) from e
|
|
13
|
-
|
|
14
|
-
import msgspec
|
|
15
|
-
|
|
16
|
-
from python3_commons.conf import oidc_settings
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class TokenData(msgspec.Struct):
|
|
22
|
-
exp: int
|
|
23
|
-
iat: int
|
|
24
|
-
iss: str
|
|
25
|
-
sub: str
|
|
26
|
-
aud: str | Sequence[str] | None = None
|
|
27
|
-
email: str | None = None
|
|
28
|
-
name: str | None = None
|
|
29
|
-
preferred_username: str | None = None
|
|
30
|
-
realm_access: dict[str, Sequence[str]] | None = None
|
|
31
|
-
resource_access: dict[str, dict[str, Sequence[str]]] | None = None
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def roles(self) -> list[str]:
|
|
35
|
-
roles_list = []
|
|
36
|
-
|
|
37
|
-
if self.realm_access:
|
|
38
|
-
roles_list.extend(self.realm_access.get('roles', []))
|
|
39
|
-
|
|
40
|
-
if self.resource_access:
|
|
41
|
-
for client in self.resource_access.values():
|
|
42
|
-
roles_list.extend(client.get('roles', []))
|
|
43
|
-
|
|
44
|
-
return list(set(roles_list))
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
T = TypeVar('T', bound=TokenData)
|
|
48
|
-
OIDC_CONFIG_URL = (
|
|
49
|
-
f'{oidc_settings.authority_internal_url or oidc_settings.authority_url}/.well-known/openid-configuration'
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async def fetch_openid_config() -> dict:
|
|
54
|
-
"""
|
|
55
|
-
Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
|
|
56
|
-
"""
|
|
57
|
-
async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
|
|
58
|
-
if response.status != HTTPStatus.OK:
|
|
59
|
-
_msg = 'Failed to fetch OpenID configuration'
|
|
60
|
-
|
|
61
|
-
raise RuntimeError(_msg)
|
|
62
|
-
|
|
63
|
-
return await response.json()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
async def fetch_jwks(jwks_uri: str) -> dict:
|
|
67
|
-
"""
|
|
68
|
-
Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
|
|
69
|
-
"""
|
|
70
|
-
if oidc_settings.authority_internal_url:
|
|
71
|
-
jwks_uri = jwks_uri.replace(str(oidc_settings.authority_url), str(oidc_settings.authority_internal_url))
|
|
72
|
-
|
|
73
|
-
async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
|
|
74
|
-
if response.status != HTTPStatus.OK:
|
|
75
|
-
_msg = 'Failed to fetch JWKS'
|
|
76
|
-
|
|
77
|
-
raise RuntimeError(_msg)
|
|
78
|
-
|
|
79
|
-
return await response.json()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgpack.py
RENAMED
|
File without changes
|
{python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgspec.py
RENAMED
|
File without changes
|
|
File without changes
|
{python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|