alpha-python 0.5.0__py3-none-any.whl → 0.6.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.
Files changed (91) hide show
  1. alpha/__init__.py +61 -5
  2. alpha/adapters/rest_api_unit_of_work.py +9 -3
  3. alpha/adapters/sqla_unit_of_work.py +23 -5
  4. alpha/containers/container.py +52 -47
  5. alpha/domain/__init__.py +12 -1
  6. alpha/domain/models/__init__.py +12 -1
  7. alpha/domain/models/base_model.py +37 -1
  8. alpha/domain/models/group.py +51 -1
  9. alpha/domain/models/life_cycle_base.py +19 -0
  10. alpha/domain/models/role.py +127 -0
  11. alpha/domain/models/user.py +81 -4
  12. alpha/encoder.py +21 -0
  13. alpha/factories/class_factories.py +49 -22
  14. alpha/factories/default_field_factory.py +1 -0
  15. alpha/factories/field_iterator.py +8 -2
  16. alpha/factories/jwt_factory.py +119 -36
  17. alpha/factories/logging_handler_factory.py +7 -5
  18. alpha/factories/model_class_factory.py +6 -3
  19. alpha/factories/models/factory_classes.py +20 -1
  20. alpha/factories/password_factory.py +32 -14
  21. alpha/factories/request_factory.py +24 -13
  22. alpha/factories/response_factory.py +5 -2
  23. alpha/factories/type_factories.py +16 -2
  24. alpha/handlers/api_generate_handler.py +32 -33
  25. alpha/handlers/run-api.sh +0 -8
  26. alpha/handlers/templates/python-flask/controller.mustache +140 -32
  27. alpha/handlers/templates/python-flask/controller_test.mustache +1 -1
  28. alpha/handlers/templates/python-flask/model.mustache +7 -1
  29. alpha/handlers/templates/python-flask/requirements.mustache +0 -19
  30. alpha/handlers/templates/python-flask/setup.mustache +0 -19
  31. alpha/infra/__init__.py +14 -5
  32. alpha/infra/connectors/__init__.py +9 -4
  33. alpha/infra/connectors/ldap_connector.py +38 -29
  34. alpha/infra/connectors/oidc_connector.py +1 -1
  35. alpha/infra/connectors/sql_alchemy.py +166 -0
  36. alpha/infra/databases/sql_alchemy.py +7 -160
  37. alpha/infra/models/filter_operators.py +6 -1
  38. alpha/infra/models/json_patch.py +31 -5
  39. alpha/infra/models/order_by.py +75 -3
  40. alpha/infra/models/query_clause.py +31 -6
  41. alpha/infra/models/search_filter.py +96 -0
  42. alpha/interfaces/__init__.py +2 -2
  43. alpha/interfaces/api_repository.py +11 -5
  44. alpha/interfaces/factories.py +5 -2
  45. alpha/interfaces/patchable.py +17 -1
  46. alpha/interfaces/providers.py +4 -4
  47. alpha/interfaces/sql_mapper.py +6 -3
  48. alpha/interfaces/sql_repository.py +128 -141
  49. alpha/interfaces/token_factory.py +13 -8
  50. alpha/interfaces/unit_of_work.py +32 -15
  51. alpha/interfaces/updatable.py +21 -0
  52. alpha/mixins/__init__.py +3 -1
  53. alpha/mixins/group_lifecycle.py +159 -0
  54. alpha/mixins/user_lifecycle.py +188 -0
  55. alpha/providers/database_provider.py +5 -4
  56. alpha/providers/ldap_provider.py +82 -66
  57. alpha/providers/models/credentials.py +28 -6
  58. alpha/providers/models/identity.py +16 -7
  59. alpha/providers/models/token.py +56 -0
  60. alpha/providers/oidc_provider.py +56 -33
  61. alpha/repositories/models/repository_model.py +1 -1
  62. alpha/repositories/rest_api_repository.py +23 -24
  63. alpha/repositories/sql_alchemy_repository.py +179 -133
  64. alpha/services/__init__.py +2 -0
  65. alpha/services/authentication_service.py +285 -57
  66. alpha/services/models/cookie.py +3 -49
  67. alpha/services/user_lifecycle_management.py +63 -0
  68. alpha/utils/__init__.py +4 -0
  69. alpha/utils/_http_codes.py +8 -0
  70. alpha/utils/cookie.py +51 -0
  71. alpha/utils/is_attrs.py +1 -1
  72. alpha/utils/is_pydantic.py +1 -1
  73. alpha/utils/logging_configurator.py +18 -16
  74. alpha/utils/logging_level_checker.py +3 -3
  75. alpha/utils/openapi_test/__init__.py +0 -0
  76. alpha/utils/openapi_test/container.py +181 -0
  77. alpha/utils/openapi_test/exceptions.py +17 -0
  78. alpha/utils/openapi_test/models.py +83 -0
  79. alpha/utils/openapi_test/orm.py +90 -0
  80. alpha/utils/openapi_test/service.py +172 -0
  81. alpha/utils/request_headers.py +116 -0
  82. alpha/utils/response_object.py +9 -7
  83. alpha/utils/secret_generator.py +2 -2
  84. {alpha_python-0.5.0.dist-info → alpha_python-0.6.0.dist-info}/METADATA +23 -6
  85. alpha_python-0.6.0.dist-info/RECORD +143 -0
  86. alpha/interfaces/updateable.py +0 -7
  87. alpha_python-0.5.0.dist-info/RECORD +0 -130
  88. {alpha_python-0.5.0.dist-info → alpha_python-0.6.0.dist-info}/WHEEL +0 -0
  89. {alpha_python-0.5.0.dist-info → alpha_python-0.6.0.dist-info}/entry_points.txt +0 -0
  90. {alpha_python-0.5.0.dist-info → alpha_python-0.6.0.dist-info}/licenses/LICENSE +0 -0
  91. {alpha_python-0.5.0.dist-info → alpha_python-0.6.0.dist-info}/top_level.txt +0 -0
alpha/__init__.py CHANGED
@@ -1,16 +1,25 @@
1
+ from importlib.metadata import version
2
+
1
3
  from alpha.adapters.rest_api_unit_of_work import RestApiUnitOfWork
2
4
  from alpha.adapters.sqla_unit_of_work import SqlAlchemyUnitOfWork
3
5
  from alpha.factories.jwt_factory import JWTFactory
4
6
  from alpha.factories.logging_handler_factory import LoggingHandlerFactory
5
7
  from alpha.factories.model_class_factory import ModelClassFactory
6
8
  from alpha.domain.models.user import User
7
- from alpha.domain.models.base_model import BaseDomainModel, DomainModel
9
+ from alpha.domain.models.group import Group
10
+ from alpha.domain.models.role import Role
11
+ from alpha.domain.models.base_model import (
12
+ BaseDomainModel,
13
+ DomainModel,
14
+ DomainModelCovariant,
15
+ DomainModelContravariant,
16
+ )
8
17
  from alpha.domain.models.life_cycle_base import LifeCycleBase
9
18
  from alpha.infra.connectors.oidc_connector import (
10
19
  OIDCConnector,
11
20
  KeyCloakOIDCConnector,
12
21
  )
13
- from alpha.infra.databases.sql_alchemy import SqlAlchemyDatabase
22
+ from alpha.infra.connectors.sql_alchemy import SqlAlchemyDatabase
14
23
  from alpha.infra.models.filter_operators import And, Or
15
24
  from alpha.infra.models.json_patch import JsonPatch
16
25
  from alpha.infra.models.order_by import OrderBy, Order
@@ -19,7 +28,7 @@ from alpha.interfaces.attrs_instance import AttrsInstance
19
28
  from alpha.interfaces.dataclass_instance import DataclassInstance
20
29
  from alpha.interfaces.pydantic_instance import PydanticInstance
21
30
  from alpha.interfaces.openapi_model import OpenAPIModel
22
- from alpha.interfaces.updateable import Updateable
31
+ from alpha.interfaces.updatable import Updatable
23
32
  from alpha.interfaces.patchable import Patchable
24
33
  from alpha.interfaces.api_repository import ApiRepository
25
34
  from alpha.interfaces.sql_repository import SqlRepository
@@ -36,6 +45,8 @@ from alpha.interfaces.providers import (
36
45
  )
37
46
  from alpha.interfaces.token_factory import TokenFactory
38
47
  from alpha.mixins.jwt_provider import JWTProviderMixin
48
+ from alpha.mixins.user_lifecycle import UserLifecycleMixin
49
+ from alpha.mixins.group_lifecycle import GroupLifecycleMixin
39
50
  from alpha.providers.models.identity import (
40
51
  Identity,
41
52
  DEFAULT_LDAP_MAPPINGS,
@@ -44,11 +55,17 @@ from alpha.providers.models.identity import (
44
55
  )
45
56
  from alpha.providers.models.credentials import PasswordCredentials
46
57
  from alpha.providers.models.token import Token
47
- from alpha.providers.oidc_provider import OIDCProvider, KeyCloakProvider
58
+ from alpha.providers.oidc_provider import (
59
+ OIDCProvider,
60
+ KeyCloakProvider,
61
+ DEFAULT_OIDC_MAPPINGS,
62
+ DEFAULT_KEYCLOAK_MAPPINGS,
63
+ )
48
64
  from alpha.repositories.models.repository_model import RepositoryModel
49
65
  from alpha.repositories.rest_api_repository import RestApiRepository
50
66
  from alpha.repositories.sql_alchemy_repository import SqlAlchemyRepository
51
67
  from alpha.services.authentication_service import AuthenticationService
68
+ from alpha.services.user_lifecycle_management import UserLifecycleManagement
52
69
  from alpha.utils.is_attrs import is_attrs
53
70
  from alpha.utils.is_pydantic import is_pydantic
54
71
  from alpha.utils.logging_configurator import (
@@ -56,11 +73,13 @@ from alpha.utils.logging_configurator import (
56
73
  GunicornLogger,
57
74
  )
58
75
  from alpha.utils.logging_level_checker import logging_level_checker
76
+ from alpha.utils.request_headers import Headers
59
77
  from alpha.utils.response_object import create_response_object
60
78
  from alpha.utils.verify_identity import verify_identity
61
79
  from alpha.utils.version_checker import minor_version_gte
62
80
  from alpha.encoder import JSONEncoder
63
81
 
82
+
64
83
  # Optional LDAP support - only import if ldap3 is available
65
84
  try:
66
85
  from alpha.infra.connectors.ldap_connector import (
@@ -75,6 +94,9 @@ try:
75
94
  except ImportError:
76
95
  _LDAP_AVAILABLE = False # type: ignore
77
96
 
97
+
98
+ __version__ = version("alpha-python")
99
+
78
100
  __all__ = [
79
101
  "RestApiUnitOfWork",
80
102
  "SqlAlchemyUnitOfWork",
@@ -83,8 +105,12 @@ __all__ = [
83
105
  "ModelClassFactory",
84
106
  "BaseDomainModel",
85
107
  "DomainModel",
108
+ "DomainModelCovariant",
109
+ "DomainModelContravariant",
86
110
  "LifeCycleBase",
87
111
  "User",
112
+ "Group",
113
+ "Role",
88
114
  "OIDCConnector",
89
115
  "KeyCloakOIDCConnector",
90
116
  "SqlAlchemyDatabase",
@@ -99,7 +125,7 @@ __all__ = [
99
125
  "DataclassInstance",
100
126
  "PydanticInstance",
101
127
  "OpenAPIModel",
102
- "Updateable",
128
+ "Updatable",
103
129
  "Patchable",
104
130
  "ApiRepository",
105
131
  "SqlRepository",
@@ -114,6 +140,8 @@ __all__ = [
114
140
  "TokenIssuer",
115
141
  "TokenFactory",
116
142
  "JWTProviderMixin",
143
+ "UserLifecycleMixin",
144
+ "GroupLifecycleMixin",
117
145
  "Identity",
118
146
  "DEFAULT_LDAP_MAPPINGS",
119
147
  "DEFAULT_AD_MAPPINGS",
@@ -122,15 +150,19 @@ __all__ = [
122
150
  "Token",
123
151
  "OIDCProvider",
124
152
  "KeyCloakProvider",
153
+ "DEFAULT_OIDC_MAPPINGS",
154
+ "DEFAULT_KEYCLOAK_MAPPINGS",
125
155
  "RepositoryModel",
126
156
  "RestApiRepository",
127
157
  "SqlAlchemyRepository",
128
158
  "AuthenticationService",
159
+ "UserLifecycleManagement",
129
160
  "is_attrs",
130
161
  "is_pydantic",
131
162
  "LoggingConfigurator",
132
163
  "GunicornLogger",
133
164
  "logging_level_checker",
165
+ "Headers",
134
166
  "create_response_object",
135
167
  "verify_identity",
136
168
  "minor_version_gte",
@@ -147,3 +179,27 @@ if _LDAP_AVAILABLE:
147
179
  "ADProvider",
148
180
  ]
149
181
  )
182
+
183
+
184
+ def _ensure_ast_str_compat() -> None:
185
+ """Provide ast.Str compatibility for Python 3.14+.
186
+
187
+ Older Werkzeug versions (used by Flask 1.x) still instantiate ast.Str.
188
+ Python 3.14 removed that alias, so we recreate it with ast.Constant.
189
+ """
190
+ if hasattr(ast, "Str"):
191
+ return
192
+
193
+ class _StrCompat(ast.Constant):
194
+ def __new__(cls, s: str = "", **kwargs): # noqa: N804
195
+ return ast.Constant(value=s, **kwargs)
196
+
197
+ setattr(ast, "Str", _StrCompat)
198
+
199
+
200
+ try:
201
+ import ast
202
+
203
+ _ensure_ast_str_compat()
204
+ except ImportError:
205
+ pass
@@ -11,7 +11,13 @@ UOW = TypeVar("UOW", bound="RestApiUnitOfWork")
11
11
 
12
12
 
13
13
  class RestApiUnitOfWork:
14
- """Unit of Work implementation for REST API interactions."""
14
+ """Unit of Work implementation for REST API interactions.
15
+
16
+ This class manages the lifecycle of a shared HTTP session and provides
17
+ access to configured repositories for API interactions. It does not support
18
+ transactional operations like commit, flush, rollback, or refresh, as these
19
+ concepts do not apply to REST API interactions.
20
+ """
15
21
 
16
22
  def __init__(
17
23
  self,
@@ -22,9 +28,9 @@ class RestApiUnitOfWork:
22
28
 
23
29
  Parameters
24
30
  ----------
25
- repos : list[RepositoryModel]
31
+ repos
26
32
  The list of repository models to use.
27
- session : requests.sessions.Session | None
33
+ session
28
34
  The requests session (or compatible HTTP client, e.g., httpx) to
29
35
  use for context management, by default None
30
36
 
@@ -12,7 +12,12 @@ UOW = TypeVar("UOW", bound="SqlAlchemyUnitOfWork")
12
12
 
13
13
 
14
14
  class SqlAlchemyUnitOfWork:
15
- """Unit of Work implementation for SQLAlchemy databases."""
15
+ """Unit of Work implementation for SQLAlchemy databases.
16
+
17
+ This class manages the lifecycle of a SQLAlchemy session and provides
18
+ access to configured repositories for database interactions. It supports
19
+ transactional operations such as commit, flush, rollback, and refresh.
20
+ """
16
21
 
17
22
  def __init__(
18
23
  self,
@@ -23,9 +28,9 @@ class SqlAlchemyUnitOfWork:
23
28
 
24
29
  Parameters
25
30
  ----------
26
- db : SqlDatabase
31
+ db
27
32
  The database instance to use.
28
- repos : list[RepositoryModel]
33
+ repos
29
34
  The list of repository models to use.
30
35
 
31
36
  Raises
@@ -45,6 +50,7 @@ class SqlAlchemyUnitOfWork:
45
50
 
46
51
  Returns
47
52
  -------
53
+ UOW
48
54
  The Unit of Work instance.
49
55
 
50
56
  Raises
@@ -107,7 +113,13 @@ class SqlAlchemyUnitOfWork:
107
113
  self._session.rollback()
108
114
 
109
115
  def refresh(self, obj: object) -> None:
110
- """Refresh the state of a given object."""
116
+ """Refresh the state of a given object.
117
+
118
+ Parameters
119
+ ----------
120
+ obj
121
+ The object to refresh.
122
+ """
111
123
  if not self._session:
112
124
  raise exceptions.DatabaseSessionError(
113
125
  "No active database session is defined"
@@ -116,5 +128,11 @@ class SqlAlchemyUnitOfWork:
116
128
 
117
129
  @property
118
130
  def session(self) -> Session | None:
119
- """Get the current database session."""
131
+ """Get the current database session.
132
+
133
+ Returns
134
+ -------
135
+ Session | None
136
+ The current database session.
137
+ """
120
138
  return self._session
@@ -8,13 +8,18 @@ from alpha.handlers.models.argument import Argument
8
8
 
9
9
 
10
10
  class Container(containers.DeclarativeContainer):
11
- """Dependency injection container for the alpha package."""
11
+ """Dependency injection container for the alpha package.
12
+
13
+ The container is used to manage the dependencies of the alpha package,
14
+ including the API generation and running commands. It allows for easy
15
+ configuration and extension of the commands and their dependencies.
16
+ """
12
17
 
13
18
  config = providers.Configuration()
14
19
 
15
20
  api_gen_command = providers.Factory(
16
21
  Command,
17
- name='gen',
22
+ name="gen",
18
23
  help=(
19
24
  "Generate the API code and watch OpenAPI spec file. The API code "
20
25
  "will be generated into the ./api folder."
@@ -23,46 +28,46 @@ class Container(containers.DeclarativeContainer):
23
28
  arguments=providers.List(
24
29
  providers.Factory(
25
30
  Argument,
26
- default='specification/openapi.yaml',
27
- name='--spec-file',
28
- help='Path to the specification file',
31
+ default="specification/openapi.yaml",
32
+ name="--spec-file",
33
+ help="Path to the specification file",
29
34
  args={
30
- 'type': str,
31
- 'nargs': '?',
35
+ "type": str,
36
+ "nargs": "?",
32
37
  },
33
38
  ),
34
39
  providers.Factory(
35
40
  Argument,
36
41
  default=config.api_package_name,
37
- name='--api-package',
42
+ name="--api-package",
38
43
  help=(
39
44
  "Name of the API package to generate. Automatically "
40
45
  "determined or guessed. If incorrect, "
41
46
  "just use this argument."
42
47
  ),
43
48
  args={
44
- 'type': str,
45
- 'nargs': '?',
49
+ "type": str,
50
+ "nargs": "?",
46
51
  },
47
52
  ),
48
53
  providers.Factory(
49
54
  Argument,
50
55
  default=config.service_package_name,
51
- name='--service-package',
56
+ name="--service-package",
52
57
  help=(
53
58
  "Name of the service package to use. Automatically "
54
59
  "determined or guessed. If incorrect, "
55
60
  "just use this argument."
56
61
  ),
57
62
  args={
58
- 'type': str,
59
- 'nargs': '?',
63
+ "type": str,
64
+ "nargs": "?",
60
65
  },
61
66
  ),
62
67
  providers.Factory(
63
68
  Argument,
64
69
  default=config.container_import,
65
- name='--container-import',
70
+ name="--container-import",
66
71
  help=(
67
72
  "Name of the container to use. Automatically "
68
73
  "determined or guessed. If incorrect, "
@@ -70,76 +75,76 @@ class Container(containers.DeclarativeContainer):
70
75
  "use empty string for this variable"
71
76
  ),
72
77
  args={
73
- 'type': str,
74
- 'nargs': '?',
78
+ "type": str,
79
+ "nargs": "?",
75
80
  },
76
81
  ),
77
82
  providers.Factory(
78
83
  Argument,
79
84
  default=config.init_container_from,
80
- name='--init-container-from',
85
+ name="--init-container-from",
81
86
  help=(
82
87
  "Location of where the container initialize function "
83
88
  "should be imported from."
84
89
  ),
85
90
  args={
86
- 'type': str,
87
- 'nargs': '?',
91
+ "type": str,
92
+ "nargs": "?",
88
93
  },
89
94
  ),
90
95
  providers.Factory(
91
96
  Argument,
92
97
  default=config.init_container_function,
93
- name='--init-container-function',
98
+ name="--init-container-function",
94
99
  help="Name of the container initialize function.",
95
100
  args={
96
- 'type': str,
97
- 'nargs': '?',
101
+ "type": str,
102
+ "nargs": "?",
98
103
  },
99
104
  ),
100
105
  providers.Factory(
101
106
  Argument,
102
- default='post_process.py',
103
- name='--post-process-file',
107
+ default="post_process.py",
108
+ name="--post-process-file",
104
109
  help="Path to the post process file to use after generation",
105
110
  args={
106
- 'type': str,
107
- 'nargs': '?',
111
+ "type": str,
112
+ "nargs": "?",
108
113
  },
109
114
  ),
110
115
  providers.Factory(
111
116
  Argument,
112
- default='python-flask',
113
- name='--generator-name',
117
+ default="python-flask",
118
+ name="--generator-name",
114
119
  help="Name of the openapi generator to use",
115
120
  args={
116
- 'type': str,
117
- 'nargs': '?',
121
+ "type": str,
122
+ "nargs": "?",
118
123
  },
119
124
  ),
120
125
  providers.Factory(
121
126
  Argument,
122
127
  default=False,
123
- name='--no-watch',
128
+ name="--no-watch",
124
129
  help=(
125
130
  "To prevent watching the spec file for changes and only "
126
131
  "run the generation once."
127
132
  ),
128
133
  args={
129
- 'action': 'store_true',
134
+ "action": "store_true",
130
135
  },
131
136
  ),
132
137
  providers.Factory(
133
138
  Argument,
134
139
  default=False,
135
- name='--templates-only',
140
+ name="--templates-only",
136
141
  help=(
137
142
  "Only create a templates folder containing openapi "
138
143
  "mustache templates in the current working directory. "
139
144
  "Skip generation of API code."
140
145
  ),
141
146
  args={
142
- 'action': 'store_true',
147
+ "action": "store_true",
143
148
  },
144
149
  ),
145
150
  ),
@@ -147,28 +152,28 @@ class Container(containers.DeclarativeContainer):
147
152
 
148
153
  api_run_command = providers.Factory(
149
154
  Command,
150
- name='run',
151
- help='Run API in development mode',
155
+ name="run",
156
+ help="Run API in development mode",
152
157
  handler=providers.Factory(ApiRunHandler),
153
158
  arguments=providers.List(
154
159
  providers.Factory(
155
160
  Argument,
156
161
  default=config.api_package_name,
157
- name='--api-package',
158
- help='Name of the API package to generate',
162
+ name="--api-package",
163
+ help="Name of the API package to generate",
159
164
  args={
160
- 'type': str,
161
- 'nargs': '?',
165
+ "type": str,
166
+ "nargs": "?",
162
167
  },
163
168
  ),
164
169
  providers.Factory(
165
170
  Argument,
166
- default='8080',
167
- name='--port',
168
- help='Port to run server on',
171
+ default="8080",
172
+ name="--port",
173
+ help="Port to run server on",
169
174
  args={
170
- 'type': str,
171
- 'nargs': '?',
175
+ "type": str,
176
+ "nargs": "?",
172
177
  },
173
178
  ),
174
179
  ),
@@ -176,8 +181,8 @@ class Container(containers.DeclarativeContainer):
176
181
 
177
182
  api_section = providers.Factory(
178
183
  Section,
179
- name='api',
180
- description='OpenAPI development commands.',
184
+ name="api",
185
+ description="OpenAPI development commands.",
181
186
  help=(
182
187
  "All commands for generating and developing an API using the "
183
188
  "OpenAPI standards"
alpha/domain/__init__.py CHANGED
@@ -1,10 +1,21 @@
1
1
  from alpha.domain.models.user import User
2
- from alpha.domain.models.base_model import BaseDomainModel, DomainModel
2
+ from alpha.domain.models.group import Group
3
+ from alpha.domain.models.role import Role
4
+ from alpha.domain.models.base_model import (
5
+ BaseDomainModel,
6
+ DomainModel,
7
+ DomainModelCovariant,
8
+ DomainModelContravariant,
9
+ )
3
10
  from alpha.domain.models.life_cycle_base import LifeCycleBase
4
11
 
5
12
  __all__ = [
6
13
  "BaseDomainModel",
7
14
  "DomainModel",
15
+ "DomainModelCovariant",
16
+ "DomainModelContravariant",
8
17
  "LifeCycleBase",
9
18
  "User",
19
+ "Group",
20
+ "Role",
10
21
  ]
@@ -1,10 +1,21 @@
1
1
  from alpha.domain.models.user import User
2
- from alpha.domain.models.base_model import BaseDomainModel, DomainModel
2
+ from alpha.domain.models.group import Group
3
+ from alpha.domain.models.role import Role
4
+ from alpha.domain.models.base_model import (
5
+ BaseDomainModel,
6
+ DomainModel,
7
+ DomainModelCovariant,
8
+ DomainModelContravariant,
9
+ )
3
10
  from alpha.domain.models.life_cycle_base import LifeCycleBase
4
11
 
5
12
  __all__ = [
6
13
  "BaseDomainModel",
7
14
  "DomainModel",
15
+ "DomainModelCovariant",
16
+ "DomainModelContravariant",
8
17
  "LifeCycleBase",
9
18
  "User",
19
+ "Group",
20
+ "Role",
10
21
  ]
@@ -12,7 +12,24 @@ DomainModelContravariant = TypeVar(
12
12
 
13
13
  @dataclass
14
14
  class BaseDomainModel:
15
+ """Base class for all domain models which can be inherited by all domain
16
+ models in the application.
17
+
18
+ This class is mainly being used to provide a common interface for all
19
+ domain models, and to provide a common method for converting the domain
20
+ model instance to a dictionary.
21
+
22
+ It has no attributes of its own.
23
+ """
24
+
15
25
  def to_dict(self) -> dict[str, Any]:
26
+ """Convert the domain model instance to a dictionary.
27
+
28
+ Returns
29
+ -------
30
+ dict[str, Any]
31
+ A dictionary representation of the domain model instance.
32
+ """
16
33
  obj: dict[str, Any] = {}
17
34
  for attr in self.__dataclass_fields__.keys():
18
35
  if not attr.startswith("_"):
@@ -22,4 +39,23 @@ class BaseDomainModel:
22
39
  return obj
23
40
 
24
41
  def update(self, obj: DomainModel) -> DomainModel:
25
- raise NotImplementedError("Subclasses must implement the update method")
42
+ """Update the current instance with the values from another instance.
43
+
44
+ Parameters
45
+ ----------
46
+ obj
47
+ Object to update the current instance with.
48
+
49
+ Returns
50
+ -------
51
+ DomainModel
52
+ The updated instance of the domain model.
53
+
54
+ Raises
55
+ ------
56
+ NotImplementedError
57
+ If the method is not implemented in the subclass.
58
+ """
59
+ raise NotImplementedError(
60
+ "Subclasses must implement the update method"
61
+ )
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime, timezone
3
- from typing import Sequence, cast
3
+ from typing import Any, Sequence, cast
4
4
  from uuid import UUID
5
5
 
6
6
  from alpha.domain.models.base_model import BaseDomainModel, DomainModel
@@ -9,12 +9,52 @@ from alpha.domain.models.life_cycle_base import LifeCycleBase
9
9
 
10
10
  @dataclass(kw_only=True)
11
11
  class Group(LifeCycleBase, BaseDomainModel):
12
+ """Group domain model which represents a group of users with specific
13
+ permissions. The Group model includes attributes for group identification,
14
+ description, permissions, and lifecycle attributes for tracking creation
15
+ and modification times.
16
+
17
+ Attributes
18
+ ----------
19
+ id
20
+ Unique identifier for the group, which can be a UUID, integer, or
21
+ string.
22
+ name
23
+ The name of the group, used for identification and display purposes.
24
+ description
25
+ A brief description of the group and its purpose.
26
+ permissions
27
+ A list of specific permissions assigned to the group, which can be used
28
+ for group-based access control. Each permission can represent a
29
+ specific action or resource that the group has access to.
30
+ is_active
31
+ A boolean flag indicating whether the group is active. Inactive groups
32
+ may not be able to be assigned to users or may not grant permissions to
33
+ users assigned to the group.
34
+ """
35
+
12
36
  id: UUID | int | str | None = None
13
37
  name: str | None = None
14
38
  description: str | None = None
15
39
  permissions: Sequence[str] | None = None
16
40
  is_active: bool = True
17
41
 
42
+ def to_dict(self) -> dict[str, Any]:
43
+ """Convert the Group instance to a dictionary.
44
+
45
+ Returns
46
+ -------
47
+ dict[str, Any]
48
+ A dictionary representation of the Group instance.
49
+ """
50
+ return {
51
+ "id": self.id,
52
+ "name": self.name,
53
+ "description": self.description,
54
+ "permissions": self.permissions,
55
+ "is_active": self.is_active,
56
+ }
57
+
18
58
  def update(self, obj: DomainModel) -> DomainModel:
19
59
  """Update the Group instance with data from another Group instance.
20
60
 
@@ -22,6 +62,16 @@ class Group(LifeCycleBase, BaseDomainModel):
22
62
  ----------
23
63
  obj
24
64
  Group object to update from.
65
+
66
+ Returns
67
+ -------
68
+ DomainModel
69
+ The updated instance of the Group.
70
+
71
+ Raises
72
+ ------
73
+ TypeError
74
+ If the provided object is not a Group instance.
25
75
  """
26
76
  if not isinstance(obj, Group):
27
77
  raise TypeError("Group.update expects a Group instance.")
@@ -4,6 +4,25 @@ from datetime import datetime
4
4
 
5
5
  @dataclass
6
6
  class LifeCycleBase:
7
+ """Base class for lifecycle model attributes which can be inherited by all
8
+ domain models in the application.
9
+
10
+ Attributes
11
+ ----------
12
+ created_by
13
+ The identifier of the user who created the instance. This can be a
14
+ string or None.
15
+ created_at
16
+ The timestamp when the instance was created. This can be a datetime
17
+ object or None.
18
+ modified_by
19
+ The identifier of the user who last modified the instance. This can be
20
+ a string or None.
21
+ modified_at
22
+ The timestamp when the instance was last modified. This can be a
23
+ datetime object or None.
24
+ """
25
+
7
26
  created_by: str | None = None
8
27
  created_at: datetime | None = None
9
28
  modified_by: str | None = None