alpha-python 0.7.5__tar.gz → 0.7.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.
Files changed (159) hide show
  1. {alpha_python-0.7.5 → alpha_python-0.7.6}/PKG-INFO +2 -2
  2. {alpha_python-0.7.5 → alpha_python-0.7.6}/pyproject.toml +2 -2
  3. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/__init__.py +2 -0
  4. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/__init__.py +2 -0
  5. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/__init__.py +2 -0
  6. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/group.py +41 -1
  7. alpha_python-0.7.6/src/alpha/domain/models/permission.py +110 -0
  8. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/user.py +66 -2
  9. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/mixins/jwt_provider.py +4 -1
  10. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/database_provider.py +8 -2
  11. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/ldap_provider.py +7 -1
  12. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/models/identity.py +9 -6
  13. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/oidc_provider.py +10 -2
  14. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/services/authentication_service.py +7 -2
  15. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/PKG-INFO +2 -2
  16. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/SOURCES.txt +1 -0
  17. {alpha_python-0.7.5 → alpha_python-0.7.6}/LICENSE +0 -0
  18. {alpha_python-0.7.5 → alpha_python-0.7.6}/README.md +0 -0
  19. {alpha_python-0.7.5 → alpha_python-0.7.6}/setup.cfg +0 -0
  20. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/adapters/__init__.py +0 -0
  21. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/adapters/rest_api_unit_of_work.py +0 -0
  22. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/adapters/sqla_unit_of_work.py +0 -0
  23. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/cli.py +0 -0
  24. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/containers/__init__.py +0 -0
  25. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/containers/container.py +0 -0
  26. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/base_model.py +0 -0
  27. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/life_cycle_base.py +0 -0
  28. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/domain/models/role.py +0 -0
  29. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/encoder.py +0 -0
  30. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/exceptions.py +0 -0
  31. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/__init__.py +0 -0
  32. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/_type_conversion_matrix.py +0 -0
  33. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/_type_mapping.py +0 -0
  34. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/class_factories.py +0 -0
  35. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/default_field_factory.py +0 -0
  36. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/field_iterator.py +0 -0
  37. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/jwt_factory.py +0 -0
  38. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/logging_handler_factory.py +0 -0
  39. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/model_class_factory.py +0 -0
  40. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/models/__init__.py +0 -0
  41. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/models/factory_classes.py +0 -0
  42. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/password_factory.py +0 -0
  43. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/request_factory.py +0 -0
  44. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/response_factory.py +0 -0
  45. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/factories/type_factories.py +0 -0
  46. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/__init__.py +0 -0
  47. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/api_generate_handler.py +0 -0
  48. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/api_run_handler.py +0 -0
  49. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/base_handler.py +0 -0
  50. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/gen-code.sh +0 -0
  51. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/models/__init__.py +0 -0
  52. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/models/argument.py +0 -0
  53. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/models/command.py +0 -0
  54. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/models/section.py +0 -0
  55. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/models/subparser.py +0 -0
  56. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/run-api.sh +0 -0
  57. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/__init__.py +0 -0
  58. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/Dockerfile.mustache +0 -0
  59. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/README.mustache +0 -0
  60. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__init__model.mustache +0 -0
  61. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__init__test.mustache +0 -0
  62. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__main__.mustache +0 -0
  63. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/base_model.mustache +0 -0
  64. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/controller.mustache +0 -0
  65. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/controller_test.mustache +0 -0
  66. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/dockerignore.mustache +0 -0
  67. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/encoder.mustache +0 -0
  68. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/git_push.sh.mustache +0 -0
  69. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/gitignore.mustache +0 -0
  70. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/model.mustache +0 -0
  71. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/openapi.mustache +0 -0
  72. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/param_type.mustache +0 -0
  73. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/requirements.mustache +0 -0
  74. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/security_controller_.mustache +0 -0
  75. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/setup.mustache +0 -0
  76. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/test-requirements.mustache +0 -0
  77. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/tox.mustache +0 -0
  78. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/travis.mustache +0 -0
  79. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/typing_utils.mustache +0 -0
  80. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/util.mustache +0 -0
  81. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/__init__.py +0 -0
  82. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/connectors/__init__.py +0 -0
  83. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/connectors/ldap_connector.py +0 -0
  84. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/connectors/oidc_connector.py +0 -0
  85. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/connectors/sql_alchemy.py +0 -0
  86. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/databases/__init__.py +0 -0
  87. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/databases/sql_alchemy.py +0 -0
  88. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/__init__.py +0 -0
  89. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/filter_operators.py +0 -0
  90. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/json_patch.py +0 -0
  91. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/order_by.py +0 -0
  92. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/query_clause.py +0 -0
  93. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/infra/models/search_filter.py +0 -0
  94. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/__init__.py +0 -0
  95. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/api_repository.py +0 -0
  96. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/attrs_instance.py +0 -0
  97. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/dataclass_instance.py +0 -0
  98. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/factories.py +0 -0
  99. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/handler.py +0 -0
  100. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/http_client.py +0 -0
  101. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/openapi_model.py +0 -0
  102. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/patchable.py +0 -0
  103. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/providers.py +0 -0
  104. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/pydantic_instance.py +0 -0
  105. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/refresh_repository.py +0 -0
  106. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/sql_database.py +0 -0
  107. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/sql_mapper.py +0 -0
  108. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/sql_repository.py +0 -0
  109. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/token_factory.py +0 -0
  110. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/unit_of_work.py +0 -0
  111. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/interfaces/updatable.py +0 -0
  112. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/mixins/__init__.py +0 -0
  113. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/mixins/group_lifecycle.py +0 -0
  114. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/mixins/user_lifecycle.py +0 -0
  115. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/__init__.py +0 -0
  116. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/api_key_provider.py +0 -0
  117. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/models/__init__.py +0 -0
  118. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/models/credentials.py +0 -0
  119. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/providers/models/token.py +0 -0
  120. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/py.typed +0 -0
  121. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/__init__.py +0 -0
  122. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/models/__init__.py +0 -0
  123. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/models/repository_model.py +0 -0
  124. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/refresh/__init__.py +0 -0
  125. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/refresh/cache_repository.py +0 -0
  126. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/refresh/database_repository.py +0 -0
  127. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/refresh/file_repository.py +0 -0
  128. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/refresh/memory_repository.py +0 -0
  129. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/rest_api_repository.py +0 -0
  130. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/repositories/sql_alchemy_repository.py +0 -0
  131. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/services/__init__.py +0 -0
  132. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/services/models/__init__.py +0 -0
  133. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/services/models/cookie.py +0 -0
  134. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/services/user_lifecycle_management.py +0 -0
  135. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/__init__.py +0 -0
  136. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/_http_codes.py +0 -0
  137. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/cookie.py +0 -0
  138. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/is_attrs.py +0 -0
  139. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/is_pydantic.py +0 -0
  140. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/logging_configurator.py +0 -0
  141. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/logging_level_checker.py +0 -0
  142. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/__init__.py +0 -0
  143. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/container.py +0 -0
  144. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/exceptions.py +0 -0
  145. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/models.py +0 -0
  146. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/orm.py +0 -0
  147. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/response.py +0 -0
  148. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/service.py +0 -0
  149. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/request_headers.py +0 -0
  150. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/response_object.py +0 -0
  151. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/secret_generator.py +0 -0
  152. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/verify_identity.py +0 -0
  153. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha/utils/version_checker.py +0 -0
  154. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/dependency_links.txt +0 -0
  155. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/entry_points.txt +0 -0
  156. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/requires.txt +0 -0
  157. {alpha_python-0.7.5 → alpha_python-0.7.6}/src/alpha_python.egg-info/top_level.txt +0 -0
  158. {alpha_python-0.7.5 → alpha_python-0.7.6}/tests/test_cli.py +0 -0
  159. {alpha_python-0.7.5 → alpha_python-0.7.6}/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.5
3
+ Version: 0.7.6
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
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
- Requires-Python: <3.15,>=3.11
19
+ Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: attrs>=25.4.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "alpha-python"
3
- version = "0.7.5"
3
+ version = "0.7.6"
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 = [
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
  license = "MIT"
20
20
  license-files = ['LICENSE']
21
- requires-python = ">=3.11, <3.15"
21
+ requires-python = ">=3.11"
22
22
  dependencies = [
23
23
  "attrs>=25.4.0",
24
24
  "gunicorn>=23.0.0",
@@ -7,6 +7,7 @@ from alpha.factories.logging_handler_factory import LoggingHandlerFactory
7
7
  from alpha.factories.model_class_factory import ModelClassFactory
8
8
  from alpha.domain.models.user import User
9
9
  from alpha.domain.models.group import Group
10
+ from alpha.domain.models.permission import Permission
10
11
  from alpha.domain.models.role import Role
11
12
  from alpha.domain.models.base_model import (
12
13
  BaseDomainModel,
@@ -123,6 +124,7 @@ __all__ = [
123
124
  "LifeCycleBase",
124
125
  "User",
125
126
  "Group",
127
+ "Permission",
126
128
  "Role",
127
129
  "OIDCConnector",
128
130
  "KeyCloakOIDCConnector",
@@ -1,5 +1,6 @@
1
1
  from alpha.domain.models.user import User
2
2
  from alpha.domain.models.group import Group
3
+ from alpha.domain.models.permission import Permission
3
4
  from alpha.domain.models.role import Role
4
5
  from alpha.domain.models.base_model import (
5
6
  BaseDomainModel,
@@ -17,5 +18,6 @@ __all__ = [
17
18
  "LifeCycleBase",
18
19
  "User",
19
20
  "Group",
21
+ "Permission",
20
22
  "Role",
21
23
  ]
@@ -1,5 +1,6 @@
1
1
  from alpha.domain.models.user import User
2
2
  from alpha.domain.models.group import Group
3
+ from alpha.domain.models.permission import Permission
3
4
  from alpha.domain.models.role import Role
4
5
  from alpha.domain.models.base_model import (
5
6
  BaseDomainModel,
@@ -17,5 +18,6 @@ __all__ = [
17
18
  "LifeCycleBase",
18
19
  "User",
19
20
  "Group",
21
+ "Permission",
20
22
  "Role",
21
23
  ]
@@ -39,6 +39,20 @@ class Group(LifeCycleBase, BaseDomainModel):
39
39
  permissions: list[str] = field(default_factory=list) # type: ignore
40
40
  is_active: bool = True
41
41
 
42
+ def __str__(self) -> str:
43
+ """Return the name-based representation of the Group instance."""
44
+ return self.name or ""
45
+
46
+ def __repr__(self) -> str:
47
+ """Return the official string representation of the Group instance."""
48
+ return (
49
+ "Group("
50
+ f"id={self.id!r}, "
51
+ f"name={self.name!r}, "
52
+ f"description={self.description!r}"
53
+ ")"
54
+ )
55
+
42
56
  def to_dict(self) -> dict[str, Any]:
43
57
  """Convert the Group instance to a dictionary.
44
58
 
@@ -47,12 +61,38 @@ class Group(LifeCycleBase, BaseDomainModel):
47
61
  dict[str, Any]
48
62
  A dictionary representation of the Group instance.
49
63
  """
64
+ permissions = cast(
65
+ list[str | dict[str, Any]],
66
+ [
67
+ p.to_dict() if hasattr(p, "to_dict") else p # type: ignore
68
+ for p in self.permissions
69
+ ],
70
+ )
71
+ created_at = (
72
+ self.created_at.isoformat()
73
+ if hasattr(self, "created_at") and self.created_at is not None
74
+ else None
75
+ )
76
+ modified_at = (
77
+ self.modified_at.isoformat()
78
+ if hasattr(self, "modified_at") and self.modified_at is not None
79
+ else None
80
+ )
81
+
50
82
  return {
51
83
  "id": self.id,
52
84
  "name": self.name,
53
85
  "description": self.description,
54
- "permissions": self.permissions,
86
+ "permissions": permissions,
55
87
  "is_active": self.is_active,
88
+ "created_by": self.created_by
89
+ if hasattr(self, "created_by") and self.created_by is not None
90
+ else None,
91
+ "created_at": created_at,
92
+ "modified_by": self.modified_by
93
+ if hasattr(self, "modified_by") and self.modified_by is not None
94
+ else None,
95
+ "modified_at": modified_at,
56
96
  }
57
97
 
58
98
  def update(self, obj: DomainModel) -> DomainModel:
@@ -0,0 +1,110 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime, timezone
3
+ from typing import Any, cast
4
+ from uuid import UUID
5
+
6
+ from alpha.domain.models.base_model import BaseDomainModel, DomainModel
7
+ from alpha.domain.models.life_cycle_base import LifeCycleBase
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class Permission(LifeCycleBase, BaseDomainModel):
12
+ """Permission class representing a system permission. This class is used to
13
+ define and manage permissions. User and group permissions are handled
14
+ through this class.
15
+
16
+ Attributes
17
+ ----------
18
+ id
19
+ Unique identifier for the permission. Can be a UUID, integer, or
20
+ string.
21
+ name
22
+ Name of the permission.
23
+ description
24
+ Description for the purpose of the permission.
25
+ """
26
+
27
+ id: UUID | int | str | None = None
28
+ name: str
29
+ description: str = ""
30
+
31
+ def __str__(self) -> str:
32
+ """Return the name of the permission.
33
+
34
+ Returns
35
+ -------
36
+ self.name value
37
+ """
38
+ return self.name
39
+
40
+ def __repr__(self) -> str:
41
+ """Return the official string representation of the object.
42
+
43
+ Returns
44
+ -------
45
+ A string representing the Permission instance.
46
+ """
47
+ return (
48
+ f"Permission(name={self.name!r}, description={self.description!r})"
49
+ )
50
+
51
+ def to_dict(self) -> dict[str, Any]:
52
+ """Convert the Permission instance to a dictionary.
53
+
54
+ Returns
55
+ -------
56
+ dict[str, Any]
57
+ A dictionary representation of the Permission instance.
58
+ """
59
+ created_at = (
60
+ self.created_at.isoformat()
61
+ if hasattr(self, "created_at") and self.created_at is not None
62
+ else None
63
+ )
64
+ modified_at = (
65
+ self.modified_at.isoformat()
66
+ if hasattr(self, "modified_at") and self.modified_at is not None
67
+ else None
68
+ )
69
+
70
+ return {
71
+ "id": self.id,
72
+ "name": self.name,
73
+ "description": self.description,
74
+ "created_by": self.created_by
75
+ if hasattr(self, "created_by") and self.created_by is not None
76
+ else None,
77
+ "created_at": created_at,
78
+ "modified_by": self.modified_by
79
+ if hasattr(self, "modified_by") and self.modified_by is not None
80
+ else None,
81
+ "modified_at": modified_at,
82
+ }
83
+
84
+ def update(self, obj: DomainModel) -> DomainModel:
85
+ """Update the Permission instance with data from another Permission
86
+ instance.
87
+
88
+ Parameters
89
+ ----------
90
+ obj
91
+ Permission object to update from.
92
+
93
+ Returns
94
+ -------
95
+ DomainModel
96
+ The updated instance of the Permission.
97
+
98
+ Raises
99
+ ------
100
+ TypeError
101
+ If the provided object is not a Permission instance.
102
+ """
103
+ if not isinstance(obj, Permission):
104
+ raise TypeError("Permission.update expects a Permission instance.")
105
+
106
+ self.name = obj.name
107
+ self.description = obj.description
108
+ self.modified_at = datetime.now(tz=timezone.utc)
109
+
110
+ return cast(DomainModel, self)
@@ -72,6 +72,32 @@ class User(LifeCycleBase, BaseDomainModel):
72
72
  is_active: bool = True
73
73
  admin: bool = False
74
74
 
75
+ def __str__(self) -> str:
76
+ """Return the username of the user.
77
+
78
+ Returns
79
+ -------
80
+ self.username value
81
+ """
82
+ return self.username or ""
83
+
84
+ def __repr__(self) -> str:
85
+ """Return the official string representation of the object.
86
+
87
+ Returns
88
+ -------
89
+ A string representing the User instance.
90
+ """
91
+ return (
92
+ "User("
93
+ f"id={self.id}, "
94
+ f"username={self.username!r}, "
95
+ f"display_name={self.display_name!r}, "
96
+ f"permissions={self.permissions!r}, "
97
+ f"groups={self.groups!r}"
98
+ ")"
99
+ )
100
+
75
101
  @classmethod
76
102
  def from_identity(cls, identity: Identity) -> Self:
77
103
  """Create a User instance from an Identity instance.
@@ -101,6 +127,35 @@ class User(LifeCycleBase, BaseDomainModel):
101
127
  dict[str, Any]
102
128
  A dictionary representation of the User instance.
103
129
  """
130
+ permissions = cast(
131
+ list[str | dict[str, Any]],
132
+ [
133
+ permission.to_dict() # type: ignore
134
+ if hasattr(permission, "to_dict")
135
+ else permission
136
+ for permission in self.permissions
137
+ ],
138
+ )
139
+ groups = cast(
140
+ list[str | dict[str, Any]],
141
+ [
142
+ group.to_dict() # type: ignore
143
+ if hasattr(group, "to_dict")
144
+ else group
145
+ for group in self.groups
146
+ ],
147
+ )
148
+ created_at = (
149
+ self.created_at.isoformat()
150
+ if hasattr(self, "created_at") and self.created_at is not None
151
+ else None
152
+ )
153
+ modified_at = (
154
+ self.modified_at.isoformat()
155
+ if hasattr(self, "modified_at") and self.modified_at is not None
156
+ else None
157
+ )
158
+
104
159
  return {
105
160
  "id": self.id,
106
161
  "username": self.username,
@@ -109,10 +164,18 @@ class User(LifeCycleBase, BaseDomainModel):
109
164
  "email": self.email,
110
165
  "phone": self.phone,
111
166
  "display_name": self.display_name,
112
- "permissions": self.permissions,
113
- "groups": self.groups,
167
+ "permissions": permissions,
168
+ "groups": groups,
114
169
  "is_active": self.is_active,
115
170
  "admin": self.admin,
171
+ "created_by": self.created_by
172
+ if hasattr(self, "created_by") and self.created_by is not None
173
+ else None,
174
+ "created_at": created_at,
175
+ "modified_by": self.modified_by
176
+ if hasattr(self, "modified_by") and self.modified_by is not None
177
+ else None,
178
+ "modified_at": modified_at,
116
179
  }
117
180
 
118
181
  def update(self, obj: DomainModel) -> DomainModel:
@@ -140,4 +203,5 @@ class User(LifeCycleBase, BaseDomainModel):
140
203
  self.modified_at = datetime.now(tz=timezone.utc)
141
204
  self.is_active = obj.is_active
142
205
  self.admin = obj.admin
206
+
143
207
  return cast(DomainModel, self)
@@ -12,6 +12,7 @@ class JWTProviderMixin:
12
12
  """
13
13
 
14
14
  token_factory: TokenFactory | None = None
15
+ _identity_model: type[Identity] = Identity
15
16
 
16
17
  def validate(self, token: Token) -> Identity:
17
18
  """Validate a token and return the associated identity
@@ -43,7 +44,7 @@ class JWTProviderMixin:
43
44
  "Token payload does not contain mandatory 'subject' field"
44
45
  )
45
46
 
46
- return Identity.from_dict(payload)
47
+ return self._identity_model.from_dict(payload)
47
48
 
48
49
  def issue_token(self, identity: Identity) -> Token:
49
50
  """Issue a token for the given identity
@@ -57,6 +58,8 @@ class JWTProviderMixin:
57
58
  -------
58
59
  Token object
59
60
  """
61
+ identity = self._identity_model.from_dict(identity.to_dict())
62
+
60
63
  if not self.token_factory:
61
64
  raise exceptions.MissingDependencyException(
62
65
  "Token factory is not configured"
@@ -18,12 +18,14 @@ class DatabaseProvider(JWTProviderMixin):
18
18
 
19
19
  protocol = "database"
20
20
  token_factory: TokenFactory | None = None
21
+ _identity_model: type[Identity] = Identity
21
22
 
22
23
  def __init__(
23
24
  self,
24
25
  uow: UnitOfWork,
25
26
  token_factory: TokenFactory | None = None,
26
27
  password_factory: PasswordFactory | None = None,
28
+ identity_model: type[Identity] = Identity,
27
29
  user_name_attribute: str = "username",
28
30
  users_repository_name: str = "users",
29
31
  ) -> None:
@@ -42,6 +44,9 @@ class DatabaseProvider(JWTProviderMixin):
42
44
  Password factory instance to handle password hashing and
43
45
  verification, by default None. If None, a default PasswordFactory
44
46
  will be used.
47
+ identity_model
48
+ Identity model class to use for representing users, by default
49
+ Identity
45
50
  user_name_attribute
46
51
  Attribute name to identify the user, by default "username"
47
52
  users_repository_name
@@ -52,6 +57,7 @@ class DatabaseProvider(JWTProviderMixin):
52
57
  self._password_factory = password_factory or PasswordFactory()
53
58
  self._user_name_attribute = user_name_attribute
54
59
  self._users_repository_name = users_repository_name
60
+ self._identity_model = identity_model
55
61
 
56
62
  def authenticate(self, credentials: PasswordCredentials) -> Identity:
57
63
  """Authenticate a user using their credentials.
@@ -73,7 +79,7 @@ class DatabaseProvider(JWTProviderMixin):
73
79
  credentials=credentials, user_repository=users
74
80
  )
75
81
 
76
- return Identity.from_user(user)
82
+ return self._identity_model.from_user(user)
77
83
 
78
84
  def get_user(self, subject: str) -> Identity:
79
85
  """Retrieve a user by their subject identifier.
@@ -95,7 +101,7 @@ class DatabaseProvider(JWTProviderMixin):
95
101
  username=subject, user_repository=users, attribute_name="id"
96
102
  )
97
103
 
98
- return Identity.from_user(user)
104
+ return self._identity_model.from_user(user)
99
105
 
100
106
  def change_password(
101
107
  self, credentials: PasswordCredentials, new_password: str
@@ -33,6 +33,7 @@ class LDAPProvider(JWTProviderMixin):
33
33
 
34
34
  protocol = "ldap"
35
35
  token_factory: TokenFactory | None = None
36
+ _identity_model: type[Identity] = Identity
36
37
 
37
38
  def __init__(
38
39
  self,
@@ -48,6 +49,7 @@ class LDAPProvider(JWTProviderMixin):
48
49
  flatten_claims: bool = True,
49
50
  auto_connect: bool = True,
50
51
  change_password_supported: bool = False,
52
+ identity_model: type[Identity] = Identity,
51
53
  additional_connector_params: dict[str, Any] | None = None,
52
54
  ) -> None:
53
55
  """Initialize LDAPProvider.
@@ -80,6 +82,9 @@ class LDAPProvider(JWTProviderMixin):
80
82
  True
81
83
  change_password_supported
82
84
  Whether the provider supports changing passwords, by default False
85
+ identity_model
86
+ Identity model class to use for representing users, by default
87
+ Identity
83
88
  additional_connector_params
84
89
  Additional parameters to pass to the LDAP connection, by default
85
90
  {"receive_timeout": 5}
@@ -96,6 +101,7 @@ class LDAPProvider(JWTProviderMixin):
96
101
  self._flatten_claims = flatten_claims
97
102
  self._auto_connect = auto_connect
98
103
  self._change_password_supported = change_password_supported
104
+ self._identity_model = identity_model
99
105
  self._additional_connector_params = additional_connector_params or {
100
106
  "receive_timeout": 5
101
107
  }
@@ -296,7 +302,7 @@ class LDAPProvider(JWTProviderMixin):
296
302
  Identity object
297
303
  """
298
304
  entry_dict = cast(dict[str, Any], entry.entry_attributes_as_dict) # type: ignore
299
- identity = Identity.from_ldap_dict(
305
+ identity = self._identity_model.from_ldap_dict(
300
306
  entry=entry_dict,
301
307
  mappings=self._identity_mappings,
302
308
  populate_claims=self._populate_claims,
@@ -125,7 +125,7 @@ class Identity:
125
125
  username: str | None
126
126
  email: str | None
127
127
  display_name: str | None
128
- groups: Sequence[str | Group]
128
+ groups: Sequence[str]
129
129
  permissions: Sequence[str]
130
130
  claims: Mapping[str, Any]
131
131
  issued_at: datetime
@@ -274,13 +274,16 @@ class Identity:
274
274
  An Identity instance populated with data from the User object.
275
275
  """
276
276
  subject = str(user.id) if user.id else user.username
277
+
277
278
  return cls(
278
279
  subject=subject, # type: ignore
279
280
  username=user.username,
280
281
  email=user.email,
281
282
  display_name=user.display_name,
282
- groups=user.groups or [],
283
- permissions=user.permissions or [],
283
+ groups=[str(group) for group in user.groups or []],
284
+ permissions=[
285
+ str(permission) for permission in user.permissions or []
286
+ ],
284
287
  claims={},
285
288
  issued_at=datetime.now(tz=timezone.utc),
286
289
  role=user.role, # type: ignore
@@ -324,10 +327,10 @@ class Identity:
324
327
  self.display_name = user.display_name
325
328
  for permission in user.permissions or []:
326
329
  self.permissions = self._append_on_sequence(
327
- self.permissions, permission
330
+ self.permissions, str(permission)
328
331
  )
329
332
  for group in user.groups or []:
330
- self.groups = self._append_on_sequence(self.groups, group)
333
+ self.groups = self._append_on_sequence(self.groups, str(group))
331
334
  self.role = user.role # type: ignore
332
335
  self.admin = user.admin
333
336
 
@@ -343,7 +346,7 @@ class Identity:
343
346
  for group in groups:
344
347
  for permission in group.permissions or []:
345
348
  self.permissions = self._append_on_sequence(
346
- self.permissions, permission
349
+ self.permissions, str(permission)
347
350
  )
348
351
 
349
352
  def to_dict(self) -> dict[str, Any]:
@@ -48,6 +48,7 @@ class OIDCProvider(JWTProviderMixin):
48
48
 
49
49
  protocol = "oidc"
50
50
  token_factory: TokenFactory | None = None
51
+ _identity_model: type[Identity] = Identity
51
52
 
52
53
  def __init__(
53
54
  self,
@@ -58,6 +59,7 @@ class OIDCProvider(JWTProviderMixin):
58
59
  populate_permissions: bool = False,
59
60
  populate_claims: bool = False,
60
61
  flatten_claims: bool = True,
62
+ identity_model: type[Identity] = Identity,
61
63
  change_password_supported: bool = False,
62
64
  ) -> None:
63
65
  """Initialize OIDCProvider.
@@ -80,6 +82,11 @@ class OIDCProvider(JWTProviderMixin):
80
82
  Whether to populate permissions on the Identity.
81
83
  populate_claims
82
84
  Whether to include raw claims on the Identity.
85
+ flatten_claims
86
+ Whether to flatten single-value claims in the Identity.
87
+ identity_model
88
+ Identity model class to use for representing users, by default
89
+ Identity
83
90
  change_password_supported
84
91
  Whether this provider supports changing passwords.
85
92
  """
@@ -92,6 +99,7 @@ class OIDCProvider(JWTProviderMixin):
92
99
  self._populate_permissions = populate_permissions
93
100
  self._populate_claims = populate_claims
94
101
  self._flatten_claims = flatten_claims
102
+ self._identity_model = identity_model
95
103
  self._change_password_supported = change_password_supported
96
104
 
97
105
  def authenticate(self, credentials: PasswordCredentials) -> Identity:
@@ -240,9 +248,9 @@ class OIDCProvider(JWTProviderMixin):
240
248
  audience = self._extract_audience(claims)
241
249
 
242
250
  if self._populate_claims and self._flatten_claims:
243
- claims = Identity.flatten_single_value_claims(claims)
251
+ claims = self._identity_model.flatten_single_value_claims(claims)
244
252
 
245
- identity = Identity(
253
+ identity = self._identity_model(
246
254
  subject=str(subject),
247
255
  username=username,
248
256
  email=self._get_claim(claims, "email"),
@@ -74,6 +74,7 @@ class AuthenticationService:
74
74
  user_model: type[User] = User,
75
75
  group_model: type[Group] = Group,
76
76
  token_model: type[Token] = Token,
77
+ identity_model: type[Identity] = Identity,
77
78
  users_repository_name: str = "users",
78
79
  groups_repository_name: str = "groups",
79
80
  refresh_repository: RefreshRepository | None = None,
@@ -147,6 +148,9 @@ class AuthenticationService:
147
148
  Group model class to use for database operations, by default Group
148
149
  token_model
149
150
  Token model class to use for database operations, by default Token
151
+ identity_model
152
+ Identity model class to use for object creation and manipulation,
153
+ by default Identity
150
154
  users_repository_name
151
155
  Name of the user repository in the UnitOfWork, by default "users"
152
156
  groups_repository_name
@@ -206,6 +210,7 @@ class AuthenticationService:
206
210
  self._user_model = user_model
207
211
  self._group_model = group_model
208
212
  self._token_model = token_model
213
+ self._identity_model = identity_model
209
214
  self._users_repository_name = users_repository_name
210
215
  self._groups_repository_name = groups_repository_name
211
216
  self._refresh_repository = (
@@ -265,7 +270,7 @@ class AuthenticationService:
265
270
  and credentials.username == self._static_user.username
266
271
  and credentials.password == self._static_user.password
267
272
  ):
268
- identity = Identity.from_user(self._static_user)
273
+ identity = self._identity_model.from_user(self._static_user)
269
274
 
270
275
  # Use the identity provider to authenticate the user and retrieve their
271
276
  # identity
@@ -464,7 +469,7 @@ class AuthenticationService:
464
469
  payload = self._identity_provider.token_factory.get_payload(
465
470
  token=auth_token, options={"verify_exp": False}
466
471
  )
467
- identity = Identity.from_dict(payload)
472
+ identity = self._identity_model.from_dict(payload)
468
473
  except Exception:
469
474
  identity = None
470
475
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpha-python
3
- Version: 0.7.5
3
+ Version: 0.7.6
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
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
- Requires-Python: <3.15,>=3.11
19
+ Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: attrs>=25.4.0
@@ -16,6 +16,7 @@ src/alpha/domain/models/__init__.py
16
16
  src/alpha/domain/models/base_model.py
17
17
  src/alpha/domain/models/group.py
18
18
  src/alpha/domain/models/life_cycle_base.py
19
+ src/alpha/domain/models/permission.py
19
20
  src/alpha/domain/models/role.py
20
21
  src/alpha/domain/models/user.py
21
22
  src/alpha/factories/__init__.py
File without changes
File without changes
File without changes