alpha-python 0.7.3__tar.gz → 0.7.5__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.
- {alpha_python-0.7.3 → alpha_python-0.7.5}/PKG-INFO +1 -1
- {alpha_python-0.7.3 → alpha_python-0.7.5}/pyproject.toml +1 -1
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/adapters/rest_api_unit_of_work.py +14 -10
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/adapters/sqla_unit_of_work.py +11 -3
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/group.py +3 -3
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/user.py +4 -4
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/request_factory.py +10 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/controller.mustache +1 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/api_repository.py +21 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/sql_repository.py +13 -2
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/mixins/user_lifecycle.py +11 -11
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/ldap_provider.py +11 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/models/identity.py +48 -9
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/oidc_provider.py +11 -1
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/database_repository.py +2 -2
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/request_headers.py +50 -2
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/PKG-INFO +1 -1
- {alpha_python-0.7.3 → alpha_python-0.7.5}/LICENSE +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/README.md +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/setup.cfg +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/adapters/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/cli.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/containers/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/containers/container.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/base_model.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/life_cycle_base.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/domain/models/role.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/encoder.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/exceptions.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/_type_conversion_matrix.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/_type_mapping.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/class_factories.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/default_field_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/field_iterator.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/jwt_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/logging_handler_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/model_class_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/models/factory_classes.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/password_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/response_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/factories/type_factories.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/api_generate_handler.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/api_run_handler.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/base_handler.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/gen-code.sh +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/models/argument.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/models/command.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/models/section.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/models/subparser.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/run-api.sh +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/Dockerfile.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/README.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/__init__model.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/__init__test.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/__main__.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/base_model.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/controller_test.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/dockerignore.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/encoder.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/git_push.sh.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/gitignore.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/model.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/openapi.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/param_type.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/requirements.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/security_controller_.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/setup.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/test-requirements.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/tox.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/travis.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/typing_utils.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/handlers/templates/python-flask/util.mustache +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/connectors/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/connectors/ldap_connector.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/connectors/oidc_connector.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/connectors/sql_alchemy.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/databases/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/databases/sql_alchemy.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/filter_operators.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/json_patch.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/order_by.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/query_clause.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/infra/models/search_filter.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/attrs_instance.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/dataclass_instance.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/factories.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/handler.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/http_client.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/openapi_model.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/patchable.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/providers.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/pydantic_instance.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/refresh_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/sql_database.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/sql_mapper.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/token_factory.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/unit_of_work.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/interfaces/updatable.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/mixins/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/mixins/group_lifecycle.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/mixins/jwt_provider.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/api_key_provider.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/database_provider.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/models/credentials.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/providers/models/token.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/py.typed +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/models/repository_model.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/cache_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/file_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/memory_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/rest_api_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/sql_alchemy_repository.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/services/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/services/authentication_service.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/services/models/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/services/models/cookie.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/services/user_lifecycle_management.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/_http_codes.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/cookie.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/is_attrs.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/is_pydantic.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/logging_configurator.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/logging_level_checker.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/__init__.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/container.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/exceptions.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/models.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/orm.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/response.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/openapi_test/service.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/response_object.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/secret_generator.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/verify_identity.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/utils/version_checker.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/SOURCES.txt +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/dependency_links.txt +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/entry_points.txt +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/requires.txt +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha_python.egg-info/top_level.txt +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/tests/test_cli.py +0 -0
- {alpha_python-0.7.3 → alpha_python-0.7.5}/tests/test_encoder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: Alpha is intended to be the first dependency you need to add to your Python application. It is a Python library which contains standard building blocks that can be used in applications that are used as APIs and/or make use of database interaction.
|
|
5
5
|
Author-email: Bart Reijling <bart@reijling.eu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "alpha-python"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.5"
|
|
4
4
|
description = "Alpha is intended to be the first dependency you need to add to your Python application. It is a Python library which contains standard building blocks that can be used in applications that are used as APIs and/or make use of database interaction."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Contains the REST API Unit of Work implementation."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, TypeVar
|
|
3
|
+
from typing import Any, TypeVar, cast
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
|
|
7
|
+
from alpha.interfaces.http_client import HTTPClient
|
|
7
8
|
from alpha.repositories.models.repository_model import RepositoryModel
|
|
8
9
|
|
|
9
10
|
|
|
@@ -22,7 +23,7 @@ class RestApiUnitOfWork:
|
|
|
22
23
|
def __init__(
|
|
23
24
|
self,
|
|
24
25
|
repos: list[RepositoryModel[Any]],
|
|
25
|
-
session:
|
|
26
|
+
session: HTTPClient | None = None,
|
|
26
27
|
) -> None:
|
|
27
28
|
"""Initialize the Unit of Work with repositories.
|
|
28
29
|
|
|
@@ -44,11 +45,12 @@ class RestApiUnitOfWork:
|
|
|
44
45
|
|
|
45
46
|
def __enter__(self: UOW) -> UOW:
|
|
46
47
|
"""Enter the REST API Unit of Work context.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
against a declared
|
|
48
|
+
|
|
49
|
+
Initializes an `HTTPClient` session if one was not provided and attaches
|
|
50
|
+
the configured repositories as attributes on the unit of work instance.
|
|
51
|
+
Each repository is constructed using the shared session and its
|
|
52
|
+
associated configuration, and optionally validated against a declared
|
|
53
|
+
interface.
|
|
52
54
|
|
|
53
55
|
Returns
|
|
54
56
|
-------
|
|
@@ -56,7 +58,9 @@ class RestApiUnitOfWork:
|
|
|
56
58
|
The configured :class:`RestApiUnitOfWork` instance to be used
|
|
57
59
|
within the context manager.
|
|
58
60
|
"""
|
|
59
|
-
self._session =
|
|
61
|
+
self._session = cast(
|
|
62
|
+
HTTPClient, self._session or requests.sessions.Session()
|
|
63
|
+
)
|
|
60
64
|
|
|
61
65
|
for repo in self._repositories:
|
|
62
66
|
name: str = repo.name
|
|
@@ -100,12 +104,12 @@ class RestApiUnitOfWork:
|
|
|
100
104
|
raise NotImplementedError("RestApiUnitOfWork does not support refresh")
|
|
101
105
|
|
|
102
106
|
@property
|
|
103
|
-
def session(self) ->
|
|
107
|
+
def session(self) -> HTTPClient | None:
|
|
104
108
|
"""Get the current session.
|
|
105
109
|
|
|
106
110
|
Returns
|
|
107
111
|
-------
|
|
108
|
-
|
|
112
|
+
HTTPClient | None
|
|
109
113
|
The current session used for API interactions.
|
|
110
114
|
"""
|
|
111
115
|
return self._session
|
|
@@ -78,14 +78,22 @@ class SqlAlchemyUnitOfWork:
|
|
|
78
78
|
|
|
79
79
|
return self
|
|
80
80
|
|
|
81
|
-
def __exit__(self,
|
|
82
|
-
"""Finalize the Unit of Work context.
|
|
81
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
82
|
+
"""Finalize the Unit of Work context.
|
|
83
|
+
|
|
84
|
+
Roll back only when leaving the context because of an exception.
|
|
85
|
+
Rolling back after a successful commit can expire ORM state and may
|
|
86
|
+
trigger DetachedInstanceError once the session is closed/detached.
|
|
87
|
+
"""
|
|
83
88
|
if not self._session:
|
|
84
89
|
raise exceptions.DatabaseSessionError(
|
|
85
90
|
"No active database session is defined"
|
|
86
91
|
)
|
|
92
|
+
|
|
93
|
+
if exc_type is not None:
|
|
94
|
+
self._session.rollback()
|
|
95
|
+
|
|
87
96
|
self._session.close()
|
|
88
|
-
self.rollback()
|
|
89
97
|
self._session = None # type: ignore
|
|
90
98
|
|
|
91
99
|
def commit(self) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, cast
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
@@ -36,7 +36,7 @@ class Group(LifeCycleBase, BaseDomainModel):
|
|
|
36
36
|
id: UUID | int | str | None = None
|
|
37
37
|
name: str | None = None
|
|
38
38
|
description: str | None = None
|
|
39
|
-
permissions:
|
|
39
|
+
permissions: list[str] = field(default_factory=list) # type: ignore
|
|
40
40
|
is_active: bool = True
|
|
41
41
|
|
|
42
42
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
-
from typing import Any, Self,
|
|
3
|
+
from typing import Any, Self, cast
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
@@ -67,8 +67,8 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
67
67
|
email: str | None = None
|
|
68
68
|
phone: str | None = None
|
|
69
69
|
display_name: str | None = None
|
|
70
|
-
permissions:
|
|
71
|
-
groups:
|
|
70
|
+
permissions: list[str] = field(default_factory=list) # type: ignore
|
|
71
|
+
groups: list[str | Group] = field(default_factory=list) # type: ignore
|
|
72
72
|
is_active: bool = True
|
|
73
73
|
admin: bool = False
|
|
74
74
|
|
|
@@ -25,6 +25,8 @@ from alpha.interfaces.factories import (
|
|
|
25
25
|
)
|
|
26
26
|
from alpha.interfaces.openapi_model import OpenAPIModel
|
|
27
27
|
from alpha.interfaces.pydantic_instance import PydanticInstance
|
|
28
|
+
from alpha.providers.models.identity import Identity
|
|
29
|
+
from alpha.utils.request_headers import Headers
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class RequestFactory:
|
|
@@ -184,6 +186,8 @@ class RequestFactory:
|
|
|
184
186
|
DataclassInstance
|
|
185
187
|
| AttrsInstance
|
|
186
188
|
| PydanticInstance
|
|
189
|
+
| Identity
|
|
190
|
+
| Headers
|
|
187
191
|
| None
|
|
188
192
|
| OpenAPIModel
|
|
189
193
|
):
|
|
@@ -202,6 +206,9 @@ class RequestFactory:
|
|
|
202
206
|
-------
|
|
203
207
|
DataclassInstance | AttrsInstance | PydanticInstance | None
|
|
204
208
|
Dataclass, attrs, or pydantic instance
|
|
209
|
+
Identity | Headers
|
|
210
|
+
If the value is an instance of Identity or Headers, it will be
|
|
211
|
+
returned as is.
|
|
205
212
|
OpenAPIModel
|
|
206
213
|
If the `use_model_class_factory` parameter is set to False, the
|
|
207
214
|
value will be returned as an OpenAPIModel instance.
|
|
@@ -211,6 +218,9 @@ class RequestFactory:
|
|
|
211
218
|
TypeError
|
|
212
219
|
The value is not an instance of the OpenAPI (Base)Model
|
|
213
220
|
"""
|
|
221
|
+
if isinstance(value, (Identity, Headers)):
|
|
222
|
+
return value
|
|
223
|
+
|
|
214
224
|
if not isinstance(value, OpenAPIModel):
|
|
215
225
|
raise TypeError(f"Unable to map {type(value)} on dataclass model")
|
|
216
226
|
|
|
@@ -198,6 +198,7 @@ def {{operationId}}(
|
|
|
198
198
|
auth_token = headers.auth_token if headers.has_auth_token else None
|
|
199
199
|
refresh_token = headers.refresh_token
|
|
200
200
|
api_key = headers.api_key
|
|
201
|
+
cookies = headers.cookies
|
|
201
202
|
|
|
202
203
|
{{#vendorExtensions.x-alpha-factory}}
|
|
203
204
|
factory = {{vendorExtensions.x-alpha-factory}}
|
|
@@ -11,6 +11,27 @@ from alpha.infra.models.json_patch import JsonPatch
|
|
|
11
11
|
class ApiRepository(Protocol[DomainModel]):
|
|
12
12
|
"""Repository contract for remote API resources."""
|
|
13
13
|
|
|
14
|
+
def request(
|
|
15
|
+
self,
|
|
16
|
+
method: str,
|
|
17
|
+
url: str,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any | None:
|
|
20
|
+
"""Make a custom API request.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
method
|
|
25
|
+
The HTTP method to use for the request (e.g., 'GET', 'POST',
|
|
26
|
+
'PATCH', 'PUT', 'DELETE').
|
|
27
|
+
url
|
|
28
|
+
The URL to which the request should be made.
|
|
29
|
+
**kwargs
|
|
30
|
+
Additional parameters to include in the API request, such as
|
|
31
|
+
headers, authentication tokens, or other request options.
|
|
32
|
+
"""
|
|
33
|
+
...
|
|
34
|
+
|
|
14
35
|
@overload
|
|
15
36
|
def add(
|
|
16
37
|
self,
|
|
@@ -3,7 +3,14 @@ SQLAlchemy ORM repository operations.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
Literal,
|
|
10
|
+
Protocol,
|
|
11
|
+
overload,
|
|
12
|
+
runtime_checkable,
|
|
13
|
+
)
|
|
7
14
|
from uuid import UUID
|
|
8
15
|
|
|
9
16
|
from sqlalchemy.orm import Session
|
|
@@ -12,7 +19,11 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
|
12
19
|
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
13
20
|
from alpha.infra.models.json_patch import JsonPatch
|
|
14
21
|
from alpha.interfaces.patchable import Patchable
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from alpha.interfaces.updatable import Updatable
|
|
25
|
+
else:
|
|
26
|
+
Updatable = Any
|
|
16
27
|
|
|
17
28
|
|
|
18
29
|
@runtime_checkable
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Contains the UserLifecycleMixin class"""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from alpha.exceptions import BadRequestException, NotFoundException
|
|
@@ -11,6 +11,8 @@ from alpha.providers.models.identity import Identity
|
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from alpha.domain.models.user import User
|
|
14
|
+
else:
|
|
15
|
+
User = Any
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class UserLifecycleMixin:
|
|
@@ -20,15 +22,13 @@ class UserLifecycleMixin:
|
|
|
20
22
|
|
|
21
23
|
uow: UnitOfWork
|
|
22
24
|
_users_repository_name: str
|
|
23
|
-
_user_model: type[
|
|
25
|
+
_user_model: type[User]
|
|
24
26
|
_password_support: bool
|
|
25
27
|
_password_factory: PasswordFactory
|
|
26
28
|
_user_username_attribute: str
|
|
27
29
|
_user_password_attribute: str
|
|
28
30
|
|
|
29
|
-
def add_user(
|
|
30
|
-
self, user: "User", identity: Identity | None = None
|
|
31
|
-
) -> "User":
|
|
31
|
+
def add_user(self, user: User, identity: Identity | None = None) -> User:
|
|
32
32
|
"""Adds a new user object to the repository
|
|
33
33
|
|
|
34
34
|
Parameters
|
|
@@ -79,7 +79,7 @@ class UserLifecycleMixin:
|
|
|
79
79
|
self.uow.commit()
|
|
80
80
|
return user
|
|
81
81
|
|
|
82
|
-
def get_user(self, user_id: str | int | UUID) ->
|
|
82
|
+
def get_user(self, user_id: str | int | UUID) -> User:
|
|
83
83
|
"""Get an user object by id from the repository
|
|
84
84
|
|
|
85
85
|
Parameters
|
|
@@ -110,7 +110,7 @@ class UserLifecycleMixin:
|
|
|
110
110
|
|
|
111
111
|
return user
|
|
112
112
|
|
|
113
|
-
def get_users(self) -> list[
|
|
113
|
+
def get_users(self) -> list[User]:
|
|
114
114
|
"""Gets all user objects from the repository
|
|
115
115
|
|
|
116
116
|
Returns
|
|
@@ -119,7 +119,7 @@ class UserLifecycleMixin:
|
|
|
119
119
|
A collection of all the user objects
|
|
120
120
|
"""
|
|
121
121
|
with self.uow:
|
|
122
|
-
users: SqlRepository[
|
|
122
|
+
users: SqlRepository[User] = getattr(
|
|
123
123
|
self.uow, self._users_repository_name
|
|
124
124
|
)
|
|
125
125
|
result = users.select()
|
|
@@ -136,7 +136,7 @@ class UserLifecycleMixin:
|
|
|
136
136
|
"""
|
|
137
137
|
user = self.get_user(user_id=user_id)
|
|
138
138
|
with self.uow:
|
|
139
|
-
users: SqlRepository[
|
|
139
|
+
users: SqlRepository[User] = getattr(
|
|
140
140
|
self.uow, self._users_repository_name
|
|
141
141
|
)
|
|
142
142
|
|
|
@@ -146,9 +146,9 @@ class UserLifecycleMixin:
|
|
|
146
146
|
def update_user(
|
|
147
147
|
self,
|
|
148
148
|
user_id: str | int | UUID,
|
|
149
|
-
user:
|
|
149
|
+
user: User,
|
|
150
150
|
identity: Identity | None = None,
|
|
151
|
-
) ->
|
|
151
|
+
) -> User:
|
|
152
152
|
"""Updates an existing user object in the repository
|
|
153
153
|
|
|
154
154
|
Parameters
|
|
@@ -45,6 +45,7 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
45
45
|
populate_groups: bool = True,
|
|
46
46
|
populate_permissions: bool = False,
|
|
47
47
|
populate_claims: bool = True,
|
|
48
|
+
flatten_claims: bool = True,
|
|
48
49
|
auto_connect: bool = True,
|
|
49
50
|
change_password_supported: bool = False,
|
|
50
51
|
additional_connector_params: dict[str, Any] | None = None,
|
|
@@ -71,6 +72,9 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
71
72
|
Whether to populate permissions in the Identity, by default False
|
|
72
73
|
populate_claims
|
|
73
74
|
Whether to populate claims in the Identity, by default True
|
|
75
|
+
flatten_claims
|
|
76
|
+
Whether to flatten single-value claims in the Identity, by default
|
|
77
|
+
True
|
|
74
78
|
auto_connect
|
|
75
79
|
Whether to automatically connect using the connector, by default
|
|
76
80
|
True
|
|
@@ -89,6 +93,7 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
89
93
|
self._populate_groups = populate_groups
|
|
90
94
|
self._populate_permissions = populate_permissions
|
|
91
95
|
self._populate_claims = populate_claims
|
|
96
|
+
self._flatten_claims = flatten_claims
|
|
92
97
|
self._auto_connect = auto_connect
|
|
93
98
|
self._change_password_supported = change_password_supported
|
|
94
99
|
self._additional_connector_params = additional_connector_params or {
|
|
@@ -297,6 +302,7 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
297
302
|
populate_claims=self._populate_claims,
|
|
298
303
|
populate_groups=self._populate_groups,
|
|
299
304
|
populate_permissions=self._populate_permissions,
|
|
305
|
+
flatten_claims=self._flatten_claims,
|
|
300
306
|
)
|
|
301
307
|
return identity
|
|
302
308
|
|
|
@@ -324,6 +330,7 @@ class ADProvider(LDAPProvider):
|
|
|
324
330
|
populate_groups: bool = True,
|
|
325
331
|
populate_permissions: bool = False,
|
|
326
332
|
populate_claims: bool = True,
|
|
333
|
+
flatten_claims: bool = True,
|
|
327
334
|
auto_connect: bool = True,
|
|
328
335
|
change_password_supported: bool = False,
|
|
329
336
|
) -> None:
|
|
@@ -356,6 +363,9 @@ class ADProvider(LDAPProvider):
|
|
|
356
363
|
populate_claims
|
|
357
364
|
Whether to populate claims on the :class:`Identity`, by default
|
|
358
365
|
True.
|
|
366
|
+
flatten_claims
|
|
367
|
+
Whether to flatten single-value claims in the Identity, by default
|
|
368
|
+
True
|
|
359
369
|
auto_connect
|
|
360
370
|
Whether to automatically open the LDAP connection on first use, by
|
|
361
371
|
default True.
|
|
@@ -373,6 +383,7 @@ class ADProvider(LDAPProvider):
|
|
|
373
383
|
populate_groups=populate_groups,
|
|
374
384
|
populate_permissions=populate_permissions,
|
|
375
385
|
populate_claims=populate_claims,
|
|
386
|
+
flatten_claims=flatten_claims,
|
|
376
387
|
auto_connect=auto_connect,
|
|
377
388
|
change_password_supported=change_password_supported,
|
|
378
389
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Mapping, Any, Self, Sequence, TYPE_CHECKING
|
|
4
|
+
from typing import Mapping, Any, Self, Sequence, TYPE_CHECKING, cast
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
|
|
7
7
|
from alpha.domain.models.group import Group
|
|
@@ -9,6 +9,8 @@ from alpha.domain.models.group import Group
|
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from alpha.domain.models.user import User
|
|
12
|
+
else:
|
|
13
|
+
User = Any
|
|
12
14
|
|
|
13
15
|
DEFAULT_LDAP_MAPPINGS = {
|
|
14
16
|
"subject": "uid",
|
|
@@ -158,7 +160,8 @@ class Identity:
|
|
|
158
160
|
populate_groups: bool = True,
|
|
159
161
|
populate_permissions: bool = False,
|
|
160
162
|
populate_claims: bool = True,
|
|
161
|
-
|
|
163
|
+
flatten_claims: bool = False,
|
|
164
|
+
) -> Identity:
|
|
162
165
|
"""Instantiate an Identity from an LDAP entry dictionary.
|
|
163
166
|
|
|
164
167
|
Parameters
|
|
@@ -176,6 +179,9 @@ class Identity:
|
|
|
176
179
|
populate_claims
|
|
177
180
|
Whether to populate the claims dictionary from the LDAP entry, by
|
|
178
181
|
default True
|
|
182
|
+
flatten_claims
|
|
183
|
+
Whether to replace claim lists containing a single value with that
|
|
184
|
+
value, by default False
|
|
179
185
|
|
|
180
186
|
Returns
|
|
181
187
|
-------
|
|
@@ -186,6 +192,12 @@ class Identity:
|
|
|
186
192
|
if not username:
|
|
187
193
|
username = cls._get_key(entry, mappings["subject"])
|
|
188
194
|
|
|
195
|
+
claims = (
|
|
196
|
+
cls._remove_password_from_claims(entry) if populate_claims else {}
|
|
197
|
+
)
|
|
198
|
+
if populate_claims and flatten_claims:
|
|
199
|
+
claims = cls.flatten_single_value_claims(claims)
|
|
200
|
+
|
|
189
201
|
return cls(
|
|
190
202
|
subject=cls._get_key(entry, mappings["subject"], ""),
|
|
191
203
|
username=username,
|
|
@@ -202,16 +214,12 @@ class Identity:
|
|
|
202
214
|
if populate_permissions
|
|
203
215
|
else []
|
|
204
216
|
),
|
|
205
|
-
claims=
|
|
206
|
-
cls._remove_password_from_claims(entry)
|
|
207
|
-
if populate_claims
|
|
208
|
-
else {}
|
|
209
|
-
),
|
|
217
|
+
claims=claims,
|
|
210
218
|
issued_at=datetime.now(tz=timezone.utc),
|
|
211
219
|
)
|
|
212
220
|
|
|
213
221
|
@classmethod
|
|
214
|
-
def from_dict(cls, data: Mapping[str, Any]) ->
|
|
222
|
+
def from_dict(cls, data: Mapping[str, Any]) -> Identity:
|
|
215
223
|
"""Instantiate an Identity from a generic dictionary.
|
|
216
224
|
|
|
217
225
|
Parameters
|
|
@@ -252,7 +260,7 @@ class Identity:
|
|
|
252
260
|
)
|
|
253
261
|
|
|
254
262
|
@classmethod
|
|
255
|
-
def from_user(cls, user: User) ->
|
|
263
|
+
def from_user(cls, user: User) -> Identity:
|
|
256
264
|
"""Instantiate an Identity from a User instance.
|
|
257
265
|
|
|
258
266
|
Parameters
|
|
@@ -428,6 +436,37 @@ class Identity:
|
|
|
428
436
|
}
|
|
429
437
|
return filtered_claims
|
|
430
438
|
|
|
439
|
+
@staticmethod
|
|
440
|
+
def flatten_single_value_claims(
|
|
441
|
+
claims: Mapping[str, Any],
|
|
442
|
+
) -> Mapping[str, Any]:
|
|
443
|
+
"""Replace single-value claim lists with their contained value.
|
|
444
|
+
|
|
445
|
+
Password-related claims (keys containing "password", case-insensitive)
|
|
446
|
+
are removed before flattening.
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
claims
|
|
451
|
+
Original claims dictionary.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
Mapping[str, Any]
|
|
456
|
+
A new claims dictionary with password-related keys removed and
|
|
457
|
+
single-value lists replaced by their contained value.
|
|
458
|
+
"""
|
|
459
|
+
claims = Identity._remove_password_from_claims(claims)
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
key: (
|
|
463
|
+
value[0]
|
|
464
|
+
if isinstance(value, list) and len(cast(list[Any], value)) == 1
|
|
465
|
+
else value
|
|
466
|
+
)
|
|
467
|
+
for key, value in claims.items()
|
|
468
|
+
}
|
|
469
|
+
|
|
431
470
|
@staticmethod
|
|
432
471
|
def _extract_groups(
|
|
433
472
|
entry: Mapping[str, Any],
|
|
@@ -57,6 +57,7 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
57
57
|
populate_groups: bool = True,
|
|
58
58
|
populate_permissions: bool = False,
|
|
59
59
|
populate_claims: bool = False,
|
|
60
|
+
flatten_claims: bool = True,
|
|
60
61
|
change_password_supported: bool = False,
|
|
61
62
|
) -> None:
|
|
62
63
|
"""Initialize OIDCProvider.
|
|
@@ -90,6 +91,7 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
90
91
|
self._populate_groups = populate_groups
|
|
91
92
|
self._populate_permissions = populate_permissions
|
|
92
93
|
self._populate_claims = populate_claims
|
|
94
|
+
self._flatten_claims = flatten_claims
|
|
93
95
|
self._change_password_supported = change_password_supported
|
|
94
96
|
|
|
95
97
|
def authenticate(self, credentials: PasswordCredentials) -> Identity:
|
|
@@ -237,6 +239,9 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
237
239
|
|
|
238
240
|
audience = self._extract_audience(claims)
|
|
239
241
|
|
|
242
|
+
if self._populate_claims and self._flatten_claims:
|
|
243
|
+
claims = Identity.flatten_single_value_claims(claims)
|
|
244
|
+
|
|
240
245
|
identity = Identity(
|
|
241
246
|
subject=str(subject),
|
|
242
247
|
username=username,
|
|
@@ -244,7 +249,7 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
244
249
|
display_name=self._get_claim(claims, "display_name"),
|
|
245
250
|
groups=groups,
|
|
246
251
|
permissions=permissions,
|
|
247
|
-
claims=
|
|
252
|
+
claims=claims if self._populate_claims else {},
|
|
248
253
|
issued_at=issued_at,
|
|
249
254
|
audience=audience,
|
|
250
255
|
role=self._get_claim(claims, "role"),
|
|
@@ -403,6 +408,7 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
403
408
|
populate_groups: bool = True,
|
|
404
409
|
populate_permissions: bool = False,
|
|
405
410
|
populate_claims: bool = False,
|
|
411
|
+
flatten_claims: bool = True,
|
|
406
412
|
change_password_supported: bool = False,
|
|
407
413
|
) -> None:
|
|
408
414
|
"""Initialize KeyCloakProvider.
|
|
@@ -427,6 +433,9 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
427
433
|
Whether to populate permissions on the Identity.
|
|
428
434
|
populate_claims
|
|
429
435
|
Whether to include raw claims on the Identity.
|
|
436
|
+
flatten_claims
|
|
437
|
+
Whether to flatten single-value claims when populating raw claims
|
|
438
|
+
on the Identity.
|
|
430
439
|
change_password_supported
|
|
431
440
|
Whether this provider supports changing passwords.
|
|
432
441
|
"""
|
|
@@ -437,5 +446,6 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
437
446
|
populate_groups=populate_groups,
|
|
438
447
|
populate_permissions=populate_permissions,
|
|
439
448
|
populate_claims=populate_claims,
|
|
449
|
+
flatten_claims=flatten_claims,
|
|
440
450
|
change_password_supported=change_password_supported,
|
|
441
451
|
)
|
{alpha_python-0.7.3 → alpha_python-0.7.5}/src/alpha/repositories/refresh/database_repository.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from alpha import exceptions
|
|
2
|
-
from alpha.
|
|
2
|
+
from alpha.interfaces.sql_database import SqlDatabase
|
|
3
3
|
from alpha.providers.models.token import Token
|
|
4
4
|
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ class DatabaseRefreshRepository:
|
|
|
15
15
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
database_connector:
|
|
18
|
+
database_connector: SqlDatabase,
|
|
19
19
|
token_model: type[Token] = Token,
|
|
20
20
|
token_max_age_seconds: int = 7 * 24 * 3600,
|
|
21
21
|
token_length: int = 32,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from http import
|
|
2
|
+
from http.cookies import SimpleCookie
|
|
3
3
|
from typing import Mapping, Self
|
|
4
4
|
|
|
5
5
|
|
|
@@ -16,12 +16,28 @@ class Headers:
|
|
|
16
16
|
|
|
17
17
|
This class is designed to be immutable and uses slots for memory
|
|
18
18
|
efficiency.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
auth_token
|
|
23
|
+
The authentication token, typically a JWT, extracted from the
|
|
24
|
+
"Authorization" header or from cookies.
|
|
25
|
+
auth_token_type
|
|
26
|
+
The type of the authentication token, typically "Bearer".
|
|
27
|
+
refresh_token
|
|
28
|
+
The refresh token, extracted from the "X-Refresh-Token" header or from
|
|
29
|
+
cookies.
|
|
30
|
+
api_key
|
|
31
|
+
The API key, extracted from the "X-API-Key" header or from cookies.
|
|
32
|
+
cookies
|
|
33
|
+
A mapping of cookie names to their values, if any cookies are present.
|
|
19
34
|
"""
|
|
20
35
|
|
|
21
36
|
auth_token: str | None = None
|
|
22
37
|
auth_token_type: str | None = None
|
|
23
38
|
refresh_token: str | None = None
|
|
24
39
|
api_key: str | None = None
|
|
40
|
+
_cookie_jar: SimpleCookie | None = None
|
|
25
41
|
|
|
26
42
|
@classmethod
|
|
27
43
|
def from_headers(
|
|
@@ -73,8 +89,9 @@ class Headers:
|
|
|
73
89
|
api_key = headers.get("X-API-Key")
|
|
74
90
|
cookies_header = headers.get("Cookie")
|
|
75
91
|
|
|
92
|
+
cookie_jar: SimpleCookie | None = None
|
|
76
93
|
if cookies_header is not None:
|
|
77
|
-
cookie_jar =
|
|
94
|
+
cookie_jar = SimpleCookie(cookies_header)
|
|
78
95
|
|
|
79
96
|
if not auth_token and auth_token_cookie_name in cookie_jar:
|
|
80
97
|
auth_token = cookie_jar[auth_token_cookie_name].value
|
|
@@ -89,6 +106,7 @@ class Headers:
|
|
|
89
106
|
auth_token_type=auth_token_type,
|
|
90
107
|
refresh_token=refresh_token,
|
|
91
108
|
api_key=api_key,
|
|
109
|
+
_cookie_jar=cookie_jar,
|
|
92
110
|
)
|
|
93
111
|
|
|
94
112
|
@property
|
|
@@ -107,6 +125,20 @@ class Headers:
|
|
|
107
125
|
def has_api_key(self) -> bool:
|
|
108
126
|
return True if self.api_key else False
|
|
109
127
|
|
|
128
|
+
@property
|
|
129
|
+
def cookies(self) -> dict[str, str]:
|
|
130
|
+
"""Return a dictionary of cookies extracted from the request headers.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
dict[str, str]
|
|
135
|
+
A dictionary mapping cookie names to their values. If no cookies
|
|
136
|
+
are present, an empty dictionary is returned.
|
|
137
|
+
"""
|
|
138
|
+
if self._cookie_jar is None:
|
|
139
|
+
return {}
|
|
140
|
+
return self._cookie_jar_to_dict(self._cookie_jar)
|
|
141
|
+
|
|
110
142
|
def __repr__(self) -> str:
|
|
111
143
|
return (
|
|
112
144
|
f"Headers(auth_token={'***' if self.auth_token else None}, "
|
|
@@ -114,3 +146,19 @@ class Headers:
|
|
|
114
146
|
f"refresh_token={'***' if self.refresh_token else None}, "
|
|
115
147
|
f"api_key={'***' if self.api_key else None})"
|
|
116
148
|
)
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def _cookie_jar_to_dict(cookie_jar: SimpleCookie) -> dict[str, str]:
|
|
152
|
+
"""Convert a SimpleCookie object to a dictionary.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
cookie_jar
|
|
157
|
+
A SimpleCookie object containing the cookies.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
dict[str, str]
|
|
162
|
+
A dictionary mapping cookie names to their values.
|
|
163
|
+
"""
|
|
164
|
+
return {key: morsel.value for key, morsel in cookie_jar.items()}
|