python3-commons 0.12.7__py3-none-any.whl → 0.13.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.

Potentially problematic release.


This version of python3-commons might be problematic. Click here for more details.

python3_commons/auth.py CHANGED
@@ -1,13 +1,10 @@
1
1
  import logging
2
- from collections.abc import Callable, Coroutine, MutableMapping, Sequence
2
+ from collections.abc import Sequence
3
3
  from http import HTTPStatus
4
- from typing import Annotated, Any, TypeVar
4
+ from typing import TypeVar
5
5
 
6
6
  import aiohttp
7
7
  import msgspec
8
- from fastapi import Depends, HTTPException
9
- from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
10
- from jose import JWTError, jwt
11
8
 
12
9
  from python3_commons.conf import oidc_settings
13
10
 
@@ -23,7 +20,6 @@ class TokenData(msgspec.Struct):
23
20
 
24
21
  T = TypeVar('T', bound=TokenData)
25
22
  OIDC_CONFIG_URL = f'{oidc_settings.authority_url}/.well-known/openid-configuration'
26
- bearer_security = HTTPBearer(auto_error=oidc_settings.enabled)
27
23
 
28
24
 
29
25
  async def fetch_openid_config() -> dict:
@@ -32,9 +28,9 @@ async def fetch_openid_config() -> dict:
32
28
  """
33
29
  async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
34
30
  if response.status != HTTPStatus.OK:
35
- raise HTTPException(
36
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch OpenID configuration'
37
- )
31
+ msg = 'Failed to fetch OpenID configuration'
32
+
33
+ raise RuntimeError(msg)
38
34
 
39
35
  return await response.json()
40
36
 
@@ -45,44 +41,8 @@ async def fetch_jwks(jwks_uri: str) -> dict:
45
41
  """
46
42
  async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
47
43
  if response.status != HTTPStatus.OK:
48
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch JWKS')
49
-
50
- return await response.json()
51
-
52
-
53
- def get_token_verifier[T](
54
- token_cls: type[T],
55
- jwks: MutableMapping,
56
- ) -> Callable[[HTTPAuthorizationCredentials], Coroutine[Any, Any, T | None]]:
57
- async def get_verified_token(
58
- authorization: Annotated[HTTPAuthorizationCredentials, Depends(bearer_security)],
59
- ) -> T | None:
60
- """
61
- Verify the JWT access token using OIDC authority JWKS.
62
- """
63
- if not oidc_settings.enabled:
64
- return None
44
+ msg = 'Failed to fetch JWKS'
65
45
 
66
- token = authorization.credentials
46
+ raise RuntimeError(msg)
67
47
 
68
- try:
69
- if not jwks:
70
- openid_config = await fetch_openid_config()
71
- _jwks = await fetch_jwks(openid_config['jwks_uri'])
72
- jwks.clear()
73
- jwks.update(_jwks)
74
-
75
- if oidc_settings.client_id:
76
- payload = jwt.decode(token, jwks, algorithms=['RS256'], audience=oidc_settings.client_id)
77
- else:
78
- payload = jwt.decode(token, jwks, algorithms=['RS256'])
79
-
80
- token_data = token_cls(**payload)
81
- except jwt.ExpiredSignatureError as e:
82
- raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail='Token has expired') from e
83
- except JWTError as e:
84
- raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=f'Token is invalid: {e!s}') from e
85
-
86
- return token_data
87
-
88
- return get_verified_token
48
+ return await response.json()
@@ -1,8 +1,4 @@
1
- from python3_commons.db.models.auth import ApiKey as ApiKey
2
- from python3_commons.db.models.auth import User as User
3
1
  from python3_commons.db.models.auth import UserGroup as UserGroup
4
- from python3_commons.db.models.rbac import RBACApiKeyRole as RBACApiKeyRole
5
2
  from python3_commons.db.models.rbac import RBACPermission as RBACPermission
6
3
  from python3_commons.db.models.rbac import RBACRole as RBACRole
7
4
  from python3_commons.db.models.rbac import RBACRolePermission as RBACRolePermission
8
- from python3_commons.db.models.rbac import RBACUserRole as RBACUserRole
@@ -1,35 +1,11 @@
1
- import uuid
2
- from datetime import datetime
3
-
4
- from fastapi_users_db_sqlalchemy import GUID, SQLAlchemyBaseUserTableUUID
5
- from sqlalchemy import BIGINT, DateTime, ForeignKey, String
1
+ from sqlalchemy import String
6
2
  from sqlalchemy.orm import Mapped, mapped_column
7
3
 
8
4
  from python3_commons.db import Base
9
- from python3_commons.db.models.common import BaseDBModel, BaseDBUUIDModel
5
+ from python3_commons.db.models.common import BaseDBModel
10
6
 
11
7
 
12
8
  class UserGroup(BaseDBModel, Base):
13
9
  __tablename__ = 'user_groups'
14
10
 
15
11
  name: Mapped[str] = mapped_column(String, nullable=False)
16
-
17
-
18
- class User(SQLAlchemyBaseUserTableUUID, Base):
19
- __tablename__ = 'users'
20
-
21
- username: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
22
- group_id: Mapped[int | None] = mapped_column(BIGINT, ForeignKey('user_groups.id'))
23
-
24
-
25
- class ApiKey(BaseDBUUIDModel, Base):
26
- __tablename__ = 'api_keys'
27
-
28
- user_id: Mapped[uuid.UUID | None] = mapped_column(
29
- GUID,
30
- ForeignKey('users.id', name='fk_api_key_user', ondelete='RESTRICT'),
31
- index=True,
32
- )
33
- partner_name: Mapped[str] = mapped_column(String, unique=True)
34
- key: Mapped[str] = mapped_column(String, unique=True)
35
- expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
@@ -1,8 +1,6 @@
1
1
  import uuid
2
- from datetime import datetime
3
2
 
4
- from fastapi_users_db_sqlalchemy import GUID
5
- from sqlalchemy import CheckConstraint, DateTime, ForeignKey, PrimaryKeyConstraint, String
3
+ from sqlalchemy import CheckConstraint, ForeignKey, PrimaryKeyConstraint, String
6
4
  from sqlalchemy.dialects.postgresql import UUID
7
5
  from sqlalchemy.orm import Mapped, mapped_column
8
6
 
@@ -40,52 +38,3 @@ class RBACRolePermission(Base):
40
38
  )
41
39
 
42
40
  __table_args__ = (PrimaryKeyConstraint('role_uid', 'permission_uid', name='pk_rbac_role_permissions'),)
43
-
44
-
45
- class RBACUserRole(Base):
46
- __tablename__ = 'rbac_user_roles'
47
-
48
- user_id: Mapped[uuid.UUID | None] = mapped_column(
49
- GUID,
50
- ForeignKey('users.id', name='fk_rbac_user_roles_user', ondelete='CASCADE'),
51
- index=True,
52
- )
53
- role_uid: Mapped[uuid.UUID | None] = mapped_column(
54
- UUID,
55
- ForeignKey('rbac_roles.uid', name='fk_rbac_user_roles_role', ondelete='CASCADE'),
56
- index=True,
57
- )
58
- starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
59
- expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
60
-
61
- __table_args__ = (PrimaryKeyConstraint('user_id', 'role_uid', name='pk_rbac_user_roles'),)
62
-
63
-
64
- class RBACApiKeyRole(Base):
65
- __tablename__ = 'rbac_api_key_roles'
66
-
67
- api_key_uid: Mapped[uuid.UUID | None] = mapped_column(
68
- UUID,
69
- ForeignKey('api_keys.uid', name='fk_rbac_api_key_roles_user', ondelete='CASCADE'),
70
- index=True,
71
- )
72
- role_uid: Mapped[uuid.UUID | None] = mapped_column(
73
- UUID,
74
- ForeignKey('rbac_roles.uid', name='fk_rbac_api_key_roles_role', ondelete='CASCADE'),
75
- index=True,
76
- )
77
- starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
78
- expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
79
-
80
- __table_args__ = (PrimaryKeyConstraint('api_key_uid', 'role_uid', name='pk_rbac_api_key_roles'),)
81
-
82
-
83
- # class RBACRoleRelation(Base):
84
- # __tablename__ = 'rbac_role_relations'
85
- #
86
- # parent_uid: Mapped[uuid.UUID] = mapped_column(UUID)
87
- # child_uid: Mapped[uuid.UUID] = mapped_column(UUID)
88
- #
89
- # __table_args__ = (
90
- # PrimaryKeyConstraint('parent_uid', 'child_uid', name='pk_rbac_role_relations'),
91
- # )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.12.7
3
+ Version: 0.13.0
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
@@ -15,14 +15,10 @@ License-File: AUTHORS.rst
15
15
  Requires-Dist: aiobotocore~=2.25.0
16
16
  Requires-Dist: aiohttp[speedups]<3.15.0,>=3.12.0
17
17
  Requires-Dist: asyncpg~=0.30.0
18
- Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
19
- Requires-Dist: fastapi-users[sqlalchemy]~=15.0.1
20
18
  Requires-Dist: lxml~=6.0.2
21
19
  Requires-Dist: msgpack~=1.1.2
22
20
  Requires-Dist: msgspec~=0.19.0
23
- Requires-Dist: pydantic[email]~=2.12.3
24
21
  Requires-Dist: pydantic-settings~=2.11.0
25
- Requires-Dist: python-jose==3.5.0
26
22
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.44
27
23
  Requires-Dist: valkey[libvalkey]~=6.1.1
28
24
  Requires-Dist: zeep~=4.3.2
@@ -1,19 +1,18 @@
1
1
  python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
2
2
  python3_commons/api_client.py,sha256=0PE8iYW5zq7n89veC6afSkwj_fzD_nldBc5wdXyg5jo,5266
3
3
  python3_commons/audit.py,sha256=vPwGR0s7RoDpxhK1Q2N44T1pdannxmSgpqcA5hn5bPE,6078
4
- python3_commons/auth.py,sha256=uJRxsmeQpJxfUs1bAbshb3wn58OdD2UjVcXAYB3hnQc,2948
4
+ python3_commons/auth.py,sha256=DJVOTFtJ8TuyMXNoMK3xtXsHx50FBdvOvo2BuQVjLJY,1239
5
5
  python3_commons/cache.py,sha256=-3CvGo1FH5H8N_gUClEcvWgz5VWU5YlgQa48PXfdmMY,7728
6
6
  python3_commons/conf.py,sha256=77nqTBL5bAk8iOEdtdHWjYx8wLKtHBj6TCylSo3BWQk,2392
7
7
  python3_commons/fs.py,sha256=dn8ZcwsQf9xcAEg6neoxLN6IzJbWpprfm8wV8S55BL0,337
8
8
  python3_commons/helpers.py,sha256=RpbHU04MXQ-b2GC0RZlmkcpu33KCU4Mq8-qa2hbNbgA,4027
9
9
  python3_commons/object_storage.py,sha256=dhTCHz_SbttB9e4wkzp3jxs-fASFa2L-F-PdkG5bedI,6584
10
- python3_commons/permissions.py,sha256=n6q4mSlBkiIeQMNFydQoXCkyu-TovAEzYK_Cvg2rU6g,1527
11
10
  python3_commons/db/__init__.py,sha256=gizEDoNNWMWyaLSpb01qXmyViKPaz1XRrk_fOZ9AveU,2918
12
11
  python3_commons/db/helpers.py,sha256=n56yYCE0fvzvU7nL1936NfZhbaQmvfumzRsGimBlNV4,1776
13
- python3_commons/db/models/__init__.py,sha256=zjZCf0DNDkqmPZ49quJ6KZohtKH87viI_ijDG3E0PVE,554
14
- python3_commons/db/models/auth.py,sha256=NMHirujigpaRR0Bhhe2gzy8Q8PPABuaA-D8ZY7aaqeE,1177
12
+ python3_commons/db/models/__init__.py,sha256=4BFR7bydZdUC4l6gEa49_fZTp4pQ5x7ndgHUBEdGwXk,290
13
+ python3_commons/db/models/auth.py,sha256=3RndBuvARojVXO7RdvJ37A0OUvHTD39gpZ3MnKXGS-c,308
15
14
  python3_commons/db/models/common.py,sha256=nRLQVi7Y0SsXo3qMIwQX6GuDO9kHnlma4O_mYXQVtHQ,1512
16
- python3_commons/db/models/rbac.py,sha256=BIB7nJXQkCes0XA-fg-oCHP6YU0_rXIm29O73j4pNUg,3160
15
+ python3_commons/db/models/rbac.py,sha256=Kvd5GNlGjaNZDlRGP_h9tb3LPHGlnY8IvfhVkFmO5Js,1333
17
16
  python3_commons/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
17
  python3_commons/log/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
19
18
  python3_commons/log/formatters.py,sha256=p2AtZD4Axp3Em0e9gWzW8U_yOR5entD7xn7Edvc-IuM,719
@@ -22,9 +21,9 @@ python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__U
22
21
  python3_commons/serializers/json.py,sha256=UPkC3ps13x2C_NxwVV-K7Ewp4VjkVHSSUkJVw5k7Wiw,712
23
22
  python3_commons/serializers/msgpack.py,sha256=mKQwfjPHh3BiXYCZLAhBsEhys8DxKHMZJIq-gDgqGDM,1451
24
23
  python3_commons/serializers/msgspec.py,sha256=CAqlaZnDh-jiT6B_TsxXF9AojROMcV3bBd0Kp8ZFAzY,2952
25
- python3_commons-0.12.7.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
26
- python3_commons-0.12.7.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
27
- python3_commons-0.12.7.dist-info/METADATA,sha256=2ON8QthJBRbW4Hu4RCMDyiGyKgilT3sTV7j-yv_uXfw,1149
28
- python3_commons-0.12.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- python3_commons-0.12.7.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
30
- python3_commons-0.12.7.dist-info/RECORD,,
24
+ python3_commons-0.13.0.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
25
+ python3_commons-0.13.0.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
26
+ python3_commons-0.13.0.dist-info/METADATA,sha256=oidcq4MnqJtRwOS2vQuOcWdiZIbWxUd8HJmA45dHOF4,977
27
+ python3_commons-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ python3_commons-0.13.0.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
29
+ python3_commons-0.13.0.dist-info/RECORD,,
@@ -1,46 +0,0 @@
1
- import logging
2
- from uuid import UUID
3
-
4
- import sqlalchemy as sa
5
- from sqlalchemy import and_, exists, func
6
- from sqlalchemy.ext.asyncio import AsyncSession
7
-
8
- from python3_commons.db.models import RBACApiKeyRole, RBACPermission, RBACRolePermission, RBACUserRole
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- async def has_api_key_permission(session: AsyncSession, api_key_uid: UUID, permission: str) -> bool:
14
- query = sa.select(
15
- exists().where(
16
- and_(
17
- RBACApiKeyRole.api_key_uid == api_key_uid,
18
- (RBACApiKeyRole.expires_at.is_(None) | (RBACApiKeyRole.expires_at > func.now())),
19
- RBACApiKeyRole.role_uid == RBACRolePermission.role_uid,
20
- RBACRolePermission.permission_uid == RBACPermission.uid,
21
- RBACPermission.name == permission,
22
- )
23
- )
24
- )
25
-
26
- cursor = await session.execute(query)
27
-
28
- return bool(cursor.scalar())
29
-
30
-
31
- async def has_user_permission(session: AsyncSession, user_id: UUID, permission: str) -> bool:
32
- query = sa.select(
33
- exists().where(
34
- and_(
35
- RBACUserRole.user_id == user_id,
36
- (RBACUserRole.expires_at.is_(None) | (RBACUserRole.expires_at > func.now())),
37
- RBACUserRole.role_uid == RBACRolePermission.role_uid,
38
- RBACRolePermission.permission_uid == RBACPermission.uid,
39
- RBACPermission.name == permission,
40
- )
41
- )
42
- )
43
-
44
- cursor = await session.execute(query)
45
-
46
- return bool(cursor.scalar())