fastapi-commons 0.8.2__tar.gz → 0.8.4__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.
Files changed (42) hide show
  1. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.pre-commit-config.yaml +2 -2
  2. {fastapi_commons-0.8.2/src/fastapi_commons.egg-info → fastapi_commons-0.8.4}/PKG-INFO +4 -3
  3. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/pyproject.toml +3 -2
  4. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/auth.py +29 -2
  5. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4/src/fastapi_commons.egg-info}/PKG-INFO +4 -3
  6. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons.egg-info/SOURCES.txt +1 -0
  7. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons.egg-info/requires.txt +3 -2
  8. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons.egg-info/scm_file_list.json +1 -0
  9. fastapi_commons-0.8.4/src/fastapi_commons.egg-info/scm_version.json +8 -0
  10. fastapi_commons-0.8.4/tests/unit/test_auth.py +55 -0
  11. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/uv.lock +219 -196
  12. fastapi_commons-0.8.2/src/fastapi_commons.egg-info/scm_version.json +0 -8
  13. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.coveragerc +0 -0
  14. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.env_template +0 -0
  15. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.github/workflows/checks.yml +0 -0
  16. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.github/workflows/python-publish.yaml +0 -0
  17. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.github/workflows/release-on-tag-push.yml +0 -0
  18. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.gitignore +0 -0
  19. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/.python-version +0 -0
  20. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/AUTHORS.rst +0 -0
  21. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/CHANGELOG.rst +0 -0
  22. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/LICENSE +0 -0
  23. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/README.md +0 -0
  24. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/README.rst +0 -0
  25. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/setup.cfg +0 -0
  26. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/__init__.py +0 -0
  27. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/conf.py +0 -0
  28. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/handlers.py +0 -0
  29. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/helpers.py +0 -0
  30. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/instrumentation.py +0 -0
  31. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/log/__init__.py +0 -0
  32. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/log/filters.py +0 -0
  33. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/middleware/__init__.py +0 -0
  34. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/middleware/correlation_id.py +0 -0
  35. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/middleware/log_context.py +0 -0
  36. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons/middleware/prometheus.py +0 -0
  37. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons.egg-info/dependency_links.txt +0 -0
  38. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/src/fastapi_commons.egg-info/top_level.txt +0 -0
  39. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/tests/__init__.py +0 -0
  40. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/tests/conftest.py +0 -0
  41. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/tests/unit/__init__.py +0 -0
  42. {fastapi_commons-0.8.2 → fastapi_commons-0.8.4}/tests/unit/test_poc.py +0 -0
@@ -1,12 +1,12 @@
1
1
  repos:
2
2
  - repo: https://github.com/astral-sh/uv-pre-commit
3
- rev: 0.12.3
3
+ rev: 0.12.5
4
4
  hooks:
5
5
  - id: uv-lock
6
6
  # - id: uv-export
7
7
 
8
8
  - repo: https://github.com/astral-sh/ruff-pre-commit
9
- rev: v0.16.2
9
+ rev: v0.16.4
10
10
  hooks:
11
11
  # Run the linter.
12
12
  - id: ruff-check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-commons
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Re-usable FastAPI code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -19,14 +19,15 @@ Requires-Dist: msgspec~=0.21.1
19
19
  Requires-Dist: pydantic[email]~=2.13.4
20
20
  Requires-Dist: pydantic-settings~=2.15.0
21
21
  Requires-Dist: python-jose~=3.5.0
22
- Requires-Dist: SQLAlchemy[asyncio]~=2.0.51
22
+ Requires-Dist: SQLAlchemy[asyncio]~=2.0.52
23
23
  Requires-Dist: opentelemetry-api~=1.44.0
24
24
  Requires-Dist: opentelemetry-sdk~=1.44.0
25
25
  Requires-Dist: opentelemetry-exporter-otlp~=1.44.0
26
26
  Requires-Dist: opentelemetry-instrumentation-fastapi~=0.65b0
27
27
  Requires-Dist: opentelemetry-instrumentation-logging~=0.65b0
28
28
  Requires-Dist: prometheus-client~=0.26.0
29
- Requires-Dist: python3-commons[authn]~=0.24.2
29
+ Requires-Dist: python3-commons[authn]~=0.24.5
30
+ Requires-Dist: starlette~=1.6.0
30
31
  Dynamic: license-file
31
32
 
32
33
  FastAPI commons
@@ -25,14 +25,15 @@ dependencies = [
25
25
  "pydantic[email]~=2.13.4",
26
26
  "pydantic-settings~=2.15.0",
27
27
  "python-jose~=3.5.0",
28
- "SQLAlchemy[asyncio]~=2.0.51",
28
+ "SQLAlchemy[asyncio]~=2.0.52",
29
29
  "opentelemetry-api~=1.44.0",
30
30
  "opentelemetry-sdk~=1.44.0",
31
31
  "opentelemetry-exporter-otlp~=1.44.0",
32
32
  "opentelemetry-instrumentation-fastapi~=0.65b0",
33
33
  "opentelemetry-instrumentation-logging~=0.65b0",
34
34
  "prometheus-client~=0.26.0",
35
- "python3-commons[authn]~=0.24.2",
35
+ "python3-commons[authn]~=0.24.5",
36
+ "starlette~=1.6.0",
36
37
  ]
37
38
 
38
39
  [dependency-groups]
@@ -5,9 +5,13 @@ from typing import Annotated, Any, TypeVar
5
5
 
6
6
  import msgspec
7
7
  from fastapi import Depends, HTTPException
8
- from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
8
+ from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
9
9
  from jose import ExpiredSignatureError, JWSError, JWTError, jwt
10
10
  from python3_commons.auth import OIDCClient, TokenData
11
+ from python3_commons.db.models.auth import ApiKey
12
+ from sqlalchemy import select
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+ from starlette import status
11
15
 
12
16
  from fastapi_commons.conf import api_auth_settings, oidc_settings
13
17
 
@@ -17,7 +21,7 @@ bearer_security = HTTPBearer(auto_error=api_auth_settings.enabled)
17
21
  oidc_client = OIDCClient(
18
22
  oidc_settings.authority_url,
19
23
  oidc_settings.client_id,
20
- oidc_settings.client_secret,
24
+ oidc_settings.client_secret.get_secret_value(),
21
25
  timeout=oidc_settings.timeout,
22
26
  verify_cert=oidc_settings.verify_cert,
23
27
  connection_limit=oidc_settings.connection_limit,
@@ -77,3 +81,26 @@ def get_token_verifier[T](
77
81
  return token_data
78
82
 
79
83
  return get_verified_token
84
+
85
+
86
+ def get_api_key_verifier(
87
+ db_dependency: Callable[..., AsyncSession], header_name: str = 'Api-Key'
88
+ ) -> Callable[..., Coroutine[Any, Any, ApiKey]]:
89
+ async def verify_api_key(
90
+ api_key: Annotated[str, Depends(APIKeyHeader(name=header_name, auto_error=True))],
91
+ session: Annotated[AsyncSession, Depends(db_dependency)],
92
+ ) -> ApiKey:
93
+ stmt = select(ApiKey).where(ApiKey.uid == api_key)
94
+ result = await session.execute(stmt)
95
+ api_key_obj = result.scalar_one_or_none()
96
+
97
+ if not api_key_obj:
98
+ raise HTTPException(
99
+ status_code=status.HTTP_401_UNAUTHORIZED,
100
+ detail='Invalid API Key',
101
+ headers={'WWW-Authenticate': 'Api-Key'},
102
+ )
103
+
104
+ return api_key_obj
105
+
106
+ return verify_api_key
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-commons
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Re-usable FastAPI code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -19,14 +19,15 @@ Requires-Dist: msgspec~=0.21.1
19
19
  Requires-Dist: pydantic[email]~=2.13.4
20
20
  Requires-Dist: pydantic-settings~=2.15.0
21
21
  Requires-Dist: python-jose~=3.5.0
22
- Requires-Dist: SQLAlchemy[asyncio]~=2.0.51
22
+ Requires-Dist: SQLAlchemy[asyncio]~=2.0.52
23
23
  Requires-Dist: opentelemetry-api~=1.44.0
24
24
  Requires-Dist: opentelemetry-sdk~=1.44.0
25
25
  Requires-Dist: opentelemetry-exporter-otlp~=1.44.0
26
26
  Requires-Dist: opentelemetry-instrumentation-fastapi~=0.65b0
27
27
  Requires-Dist: opentelemetry-instrumentation-logging~=0.65b0
28
28
  Requires-Dist: prometheus-client~=0.26.0
29
- Requires-Dist: python3-commons[authn]~=0.24.2
29
+ Requires-Dist: python3-commons[authn]~=0.24.5
30
+ Requires-Dist: starlette~=1.6.0
30
31
  Dynamic: license-file
31
32
 
32
33
  FastAPI commons
@@ -35,4 +35,5 @@ src/fastapi_commons/middleware/prometheus.py
35
35
  tests/__init__.py
36
36
  tests/conftest.py
37
37
  tests/unit/__init__.py
38
+ tests/unit/test_auth.py
38
39
  tests/unit/test_poc.py
@@ -5,11 +5,12 @@ msgspec~=0.21.1
5
5
  pydantic[email]~=2.13.4
6
6
  pydantic-settings~=2.15.0
7
7
  python-jose~=3.5.0
8
- SQLAlchemy[asyncio]~=2.0.51
8
+ SQLAlchemy[asyncio]~=2.0.52
9
9
  opentelemetry-api~=1.44.0
10
10
  opentelemetry-sdk~=1.44.0
11
11
  opentelemetry-exporter-otlp~=1.44.0
12
12
  opentelemetry-instrumentation-fastapi~=0.65b0
13
13
  opentelemetry-instrumentation-logging~=0.65b0
14
14
  prometheus-client~=0.26.0
15
- python3-commons[authn]~=0.24.2
15
+ python3-commons[authn]~=0.24.5
16
+ starlette~=1.6.0
@@ -29,6 +29,7 @@
29
29
  "tests/__init__.py",
30
30
  "tests/conftest.py",
31
31
  "tests/unit/__init__.py",
32
+ "tests/unit/test_auth.py",
32
33
  "tests/unit/test_poc.py",
33
34
  "uv.lock"
34
35
  ]
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.8.4",
3
+ "distance": 0,
4
+ "node": "gdd7b1d3d9a6243ca8443b5791d7e4186be0424b9",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-24"
8
+ }
@@ -0,0 +1,55 @@
1
+ from http import HTTPStatus
2
+ from typing import Annotated
3
+ from unittest.mock import AsyncMock, MagicMock
4
+
5
+ import pytest
6
+ from fastapi import HTTPException
7
+ from python3_commons.db.models.auth import ApiKey
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+
10
+ from fastapi_commons.auth import get_api_key_verifier
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_create_api_key_verifier_success() -> None:
15
+ # Mock database session
16
+ mock_session = AsyncMock(spec=AsyncSession)
17
+ mock_api_key = MagicMock(spec=ApiKey)
18
+ mock_api_key.uid = 'test-key'
19
+
20
+ # Mock result
21
+ mock_result = MagicMock()
22
+ mock_result.scalar_one_or_none.return_value = mock_api_key
23
+ mock_session.execute.return_value = mock_result
24
+
25
+ # Create verifier
26
+ # We pass a mock dependency. For direct calling, we just need Any.
27
+ verifier = get_api_key_verifier(lambda: mock_session)
28
+
29
+ # Test the verifier directly
30
+ result = await verifier(api_key='test-key', session=mock_session)
31
+ assert result == mock_api_key
32
+
33
+ # Verify DB call
34
+ mock_session.execute.assert_called_once()
35
+
36
+
37
+ @pytest.mark.asyncio
38
+ async def test_create_api_key_verifier_fail() -> None:
39
+ # Mock database session
40
+ mock_session = AsyncMock(spec=AsyncSession)
41
+
42
+ # Mock result to return None
43
+ mock_result = MagicMock()
44
+ mock_result.scalar_one_or_none.return_value = None
45
+ mock_session.execute.return_value = mock_result
46
+
47
+ # Create verifier
48
+ verifier = get_api_key_verifier(lambda: mock_session)
49
+
50
+ # Test the verifier directly and expect HTTPException
51
+ with pytest.raises(HTTPException) as excinfo:
52
+ await verifier(api_key='invalid-key', session=mock_session)
53
+
54
+ assert excinfo.value.status_code == HTTPStatus.UNAUTHORIZED
55
+ assert excinfo.value.detail == 'Invalid API Key'