alpha-python 0.6.0__py3-none-any.whl → 0.6.2__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.
alpha/__init__.py CHANGED
@@ -20,7 +20,7 @@ from alpha.infra.connectors.oidc_connector import (
20
20
  KeyCloakOIDCConnector,
21
21
  )
22
22
  from alpha.infra.connectors.sql_alchemy import SqlAlchemyDatabase
23
- from alpha.infra.models.filter_operators import And, Or
23
+ from alpha.infra.models.filter_operators import And, Or, FilterOperator
24
24
  from alpha.infra.models.json_patch import JsonPatch
25
25
  from alpha.infra.models.order_by import OrderBy, Order
26
26
  from alpha.infra.models.search_filter import SearchFilter, Operator
@@ -116,6 +116,7 @@ __all__ = [
116
116
  "SqlAlchemyDatabase",
117
117
  "And",
118
118
  "Or",
119
+ "FilterOperator",
119
120
  "JsonPatch",
120
121
  "OrderBy",
121
122
  "Order",
alpha/cli.py CHANGED
@@ -20,18 +20,18 @@ def main(sections: list[Section] = Provide[Container.sections]) -> None:
20
20
 
21
21
  # Create the main parser
22
22
  parser = argparse.ArgumentParser(
23
- description='Alpha command line interface.',
23
+ description="Alpha command line interface.",
24
24
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
25
25
  )
26
26
  subparsers = parser.add_subparsers(
27
- title='Use one of the sub categories to run commands on',
28
- dest='section',
27
+ title="Use one of the sub categories to run commands on",
28
+ dest="section",
29
29
  )
30
30
 
31
31
  # Parser dict to store in all parsers to match later on when finding a
32
32
  # handler.
33
- section_parsers = {}
34
- command_parsers = {}
33
+ section_parsers: dict[str, argparse.ArgumentParser] = {}
34
+ command_parsers: dict[str, dict[str, argparse.ArgumentParser]] = {}
35
35
 
36
36
  # Create all sections
37
37
  for section in sections:
@@ -43,7 +43,7 @@ def main(sections: list[Section] = Provide[Container.sections]) -> None:
43
43
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
44
44
  )
45
45
  section_subparser = section_parsers[section.name].add_subparsers(
46
- dest='command'
46
+ dest="command"
47
47
  )
48
48
 
49
49
  # Create all commands per section
@@ -88,8 +88,8 @@ def main(sections: list[Section] = Provide[Container.sections]) -> None:
88
88
  # Get all arguments and remove the section and command so they can be
89
89
  # 'kwarged' into the Handler set_arguments function.
90
90
  handler_args = vars(args)
91
- del handler_args['section']
92
- del handler_args['command']
91
+ del handler_args["section"]
92
+ del handler_args["command"]
93
93
 
94
94
  # Find the right handler
95
95
  for section in sections:
@@ -102,7 +102,7 @@ def main(sections: list[Section] = Provide[Container.sections]) -> None:
102
102
  command.handler.handle_command()
103
103
  return
104
104
 
105
- print('No handler found, you should never read this')
105
+ print("No handler found, you should never read this")
106
106
  return
107
107
 
108
108
 
@@ -118,53 +118,57 @@ def _guess_current_package_name() -> str:
118
118
  """
119
119
 
120
120
  cwd = os.getcwd()
121
- pyproject_path = os.path.join(cwd, 'pyproject.toml')
121
+ pyproject_path = os.path.join(cwd, "pyproject.toml")
122
122
 
123
123
  # look for pyproject.toml file in subfolders
124
124
  if not os.path.isfile(pyproject_path):
125
125
  for entry in os.scandir(cwd):
126
126
  if not entry.is_dir():
127
127
  continue
128
- possible_path = os.path.join(entry.path, 'pyproject.toml')
128
+ possible_path = os.path.join(entry.path, "pyproject.toml")
129
129
  if os.path.isfile(possible_path):
130
130
  pyproject_path = possible_path
131
131
  break
132
132
 
133
133
  if os.path.isfile(pyproject_path):
134
134
  try:
135
- with open(pyproject_path, 'rb') as f:
135
+ with open(pyproject_path, "rb") as f:
136
136
  pyproject_data = tomllib.load(f)
137
137
  name = None
138
138
  try:
139
- if 'project' in pyproject_data:
140
- name = pyproject_data['project']['name']
141
- elif 'tool' in pyproject_data and 'poetry' in pyproject_data['tool']:
142
- name = pyproject_data['tool']['poetry']['name']
139
+ if "project" in pyproject_data:
140
+ name = pyproject_data["project"]["name"]
141
+ elif (
142
+ "tool" in pyproject_data
143
+ and "poetry" in pyproject_data["tool"]
144
+ ):
145
+ name = pyproject_data["tool"]["poetry"]["name"]
143
146
  if name is not None:
144
- return name.replace('-', '_')
147
+ return name.replace("-", "_")
145
148
  except KeyError:
146
- print('Could not find project name in pyproject.toml')
149
+ print("Could not find project name in pyproject.toml")
147
150
  except Exception:
148
151
  pass
149
152
  else:
150
- print('Could not find pyproject.toml')
153
+ print("Could not find pyproject.toml")
151
154
 
152
155
  # Fallback to use the current folder name
153
- print('Guessing package name from folder')
156
+ print("Guessing package name from folder")
154
157
  return os.path.basename(cwd)
155
158
 
159
+
156
160
  def init() -> None:
157
161
  """Init the container and wire it to the main function."""
158
162
  container = Container()
159
163
  guessed_name = _guess_current_package_name()
160
164
  if guessed_name:
161
- container.config.api_package_name.from_value(f'{guessed_name}_api')
165
+ container.config.api_package_name.from_value(f"{guessed_name}_api")
162
166
  container.config.service_package_name.from_value(guessed_name)
163
167
  container.config.container_import.from_value(
164
- f'from {guessed_name}.containers.container import Container'
168
+ f"from {guessed_name}.containers.container import Container"
165
169
  )
166
170
  container.config.init_container_from.from_value(guessed_name)
167
- container.config.init_container_function.from_value('init_container')
171
+ container.config.init_container_function.from_value("init_container")
168
172
  container.wire(modules=[sys.modules[__name__]])
169
173
 
170
174
  main()
alpha/encoder.py CHANGED
@@ -10,9 +10,10 @@ from json import encoder
10
10
  from typing import Any
11
11
  from uuid import UUID
12
12
 
13
- import numpy as np
14
- import pandas as pd
15
- import six
13
+ import numpy as np # type: ignore[import-untyped]
14
+ import pandas as pd # type: ignore[import-untyped]
15
+ import six # type: ignore[import-untyped]
16
+
16
17
 
17
18
  from alpha.interfaces.openapi_model import OpenAPIModel
18
19
 
@@ -34,7 +34,10 @@ class JWTFactory:
34
34
  Parameters
35
35
  ----------
36
36
  secret
37
- The secret key used to sign the JWT.
37
+ The secret key used to sign the JWT. A secret value should be a
38
+ minimum of 32 characters for security reasons. This value should be
39
+ kept confidential and not exposed in the source code or version
40
+ control system.
38
41
  lifetime_hours
39
42
  The lifetime of the JWT in hours, by default None. This parameter
40
43
  is ignored if lifetime_seconds is provided. The parameter is
@@ -71,13 +74,13 @@ class JWTFactory:
71
74
  stacklevel=2,
72
75
  )
73
76
 
74
- if lifetime_seconds is None and lifetime_hours is None:
75
- lifetime_seconds = (
76
- 900 # Default to 15 minutes if no lifetime is provided
77
- )
78
- elif lifetime_seconds is None and lifetime_hours is not None:
77
+ if lifetime_seconds is None and lifetime_hours is not None:
79
78
  lifetime_seconds = 3600 * int(lifetime_hours)
80
79
 
80
+ lifetime_seconds = (
81
+ 900 if lifetime_seconds is None else lifetime_seconds
82
+ ) # Default to 15 minutes if no lifetime is provided
83
+
81
84
  self.JWT_SECRET: str = secret
82
85
  self.JWT_ISSUER = issuer
83
86
  self.JWT_ALGORITHM = jwt_algorithm
@@ -113,7 +116,7 @@ class JWTFactory:
113
116
  The generated JWT token as a string.
114
117
  """
115
118
  now = datetime.now(tz=timezone.utc)
116
- exp = now + timedelta(seconds=self.JWT_LIFETIME_SECONDS)
119
+ exp = now + timedelta(seconds=float(self.JWT_LIFETIME_SECONDS))
117
120
 
118
121
  token_payload: dict[str, Any] = {
119
122
  "sub": subject,
@@ -11,6 +11,11 @@ from alpha import exceptions
11
11
  class PasswordFactory:
12
12
  """This class provides methods for hashing and verifying passwords using
13
13
  the argon2 library.
14
+
15
+ Keep in mind that changing the password hasher in a production environment
16
+ may lead to issues with existing password hashes, so it is recommended to
17
+ use the default hasher unless you have specific requirements. Or configure
18
+ the password hasher before you deploy your application to production.
14
19
  """
15
20
 
16
21
  def __init__(
@@ -22,7 +27,9 @@ class PasswordFactory:
22
27
  ----------
23
28
  password_hasher
24
29
  An optional password hasher instance. If not provided, a default
25
- argon2.PasswordHasher with a salt length of 16 will be used.
30
+ argon2.PasswordHasher with a salt length of 16 will be used. This
31
+ allows for flexibility in case you want to use a custom password
32
+ hasher.
26
33
  """
27
34
  self._password_hasher = password_hasher or argon2.PasswordHasher(
28
35
  salt_len=16
@@ -187,6 +187,11 @@ class ApiGenerateHandler(BaseHandler):
187
187
  """Copy mustache templates to the templates folder in the working
188
188
  directory
189
189
  """
190
+ if self.generator_name is None:
191
+ raise InvalidArgumentsException(
192
+ "Generator name is required to copy templates."
193
+ )
194
+
190
195
  shutil.rmtree(self.project_templates_folder, ignore_errors=True)
191
196
  shutil.copytree(
192
197
  src=os.path.join(
@@ -6,6 +6,7 @@ import os
6
6
  import socket
7
7
 
8
8
  import connexion
9
+ from connexion import request
9
10
  {{#featureCORS}}
10
11
  from flask_cors import CORS
11
12
  {{/featureCORS}}
@@ -56,9 +57,13 @@ LoggingConfigurator(
56
57
  )
57
58
  {{/disableLoggingConfigurator}}
58
59
  {{#featureCORS}}
59
- # add CORS support
60
60
  cors_origins = container.config.cors.origins()
61
- logging.info(f'Enabling CORS for origins: {cors_origins}')
61
+ if cors_origins is None:
62
+ logging.info(
63
+ "CORS origins not configured, defaulting to allow all origins"
64
+ )
65
+ cors_origins = ["*"]
66
+ logging.info(f"Enabling CORS for origins: {cors_origins}")
62
67
  CORS(app.app, origins=cors_origins)
63
68
  {{/featureCORS}}
64
69
 
@@ -68,15 +73,27 @@ app.container = container
68
73
 
69
74
  @app.app.after_request
70
75
  def add_headers(response: Response):
71
- response.headers["Cache-Control"] = "no-store"
72
- # response.headers["Content-Security-Policy"] = "default-src 'self'"
73
- response.headers["Strict-Transport-Security"] = (
74
- "max-age=31536000; includeSubDomains"
76
+ response_headers = container.config.response.headers()
77
+ is_swagger_ui_request = request.path == "/ui" or request.path.startswith(
78
+ "/ui/"
75
79
  )
76
- response.headers["X-Content-Type-Options"] = "nosniff"
77
- response.headers["X-Frame-Options"] = "DENY"
78
- return response
79
80
 
81
+ if response_headers is None:
82
+ response_headers = {
83
+ "Cache-Control": "no-store",
84
+ "Content-Security-Policy": "default-src 'self'",
85
+ "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
86
+ "X-Content-Type-Options": "nosniff",
87
+ "X-Frame-Options": "DENY",
88
+ }
89
+ for header, value in response_headers.items():
90
+ if (
91
+ is_swagger_ui_request
92
+ and header.lower() == "content-security-policy"
93
+ ):
94
+ continue
95
+ response.headers[header] = value
96
+ return response
80
97
 
81
98
  logging.info(f'Started {{servicePackage}} API on \'{socket.gethostname()}\'')
82
99
 
@@ -43,9 +43,7 @@ from {{packageName}} import models as api_models
43
43
 
44
44
  {{#operations}}
45
45
  {{#operation}}
46
- {{#authMethods}}{{#vendorExtensions.x-alpha-service-name}}@inject{{/vendorExtensions.x-alpha-service-name}}{{/authMethods}}
47
- {{^authMethods}}{{#vendorExtensions.x-alpha-service-name}}@inject{{/vendorExtensions.x-alpha-service-name}}{{/authMethods}}
48
- {{#authMethods}}{{^vendorExtensions.x-alpha-service-name}}@inject{{/vendorExtensions.x-alpha-service-name}}{{/authMethods}}
46
+ {{#authMethods}}{{#-first}}@inject{{/-first}}{{/authMethods}}{{^authMethods}}{{#vendorExtensions.x-alpha-service-name}}@inject{{/vendorExtensions.x-alpha-service-name}}{{/authMethods}}
49
47
  def {{operationId}}(
50
48
  {{#allParams}}{{^isBodyParam}}{{paramName}}{{^required}}=None{{/required}},{{/isBodyParam}}{{/allParams}}
51
49
  {{#authMethods}}{{#isBasicBearer}}token_factory=Provide[Container.token_factory],{{/isBasicBearer}}{{/authMethods}}
@@ -214,6 +212,8 @@ def {{operationId}}(
214
212
  {{/allParams}}
215
213
  {{/vendorExtensions.x-alpha-factory}}
216
214
 
215
+ response_object_function = {{^vendorExtensions.x-alpha-custom-response-builder}}create_response_object{{/vendorExtensions.x-alpha-custom-response-builder}}{{#vendorExtensions.x-alpha-custom-response-builder}}{{vendorExtensions.x-alpha-custom-response-builder}}{{/vendorExtensions.x-alpha-custom-response-builder}}
216
+
217
217
  try:
218
218
  # Objects used for authorization
219
219
  roles=[{{#vendorExtensions.x-alpha-verify-roles}}"{{.}}",{{/vendorExtensions.x-alpha-verify-roles}}]
@@ -254,7 +254,7 @@ def {{operationId}}(
254
254
  {{/vendorExtensions.x-alpha-service-name}}
255
255
  {{^vendorExtensions.x-alpha-service-name}}
256
256
  {{#vendorExtensions.x-alpha-custom-function}}
257
- result = {{vendorExtensions.x-alpha-custom-function}}
257
+ result = {{{vendorExtensions.x-alpha-custom-function}}}
258
258
  {{/vendorExtensions.x-alpha-custom-function}}
259
259
  {{/vendorExtensions.x-alpha-service-name}}
260
260
  {{/vendorExtensions.x-alpha-request-factory}}
@@ -270,7 +270,7 @@ def {{operationId}}(
270
270
  {{/vendorExtensions.x-alpha-custom-function}}
271
271
  {{/vendorExtensions.x-alpha-service-method}}
272
272
  {{#vendorExtensions.x-alpha-custom-function}}
273
- result = {{vendorExtensions.x-alpha-custom-function}}
273
+ result = {{{vendorExtensions.x-alpha-custom-function}}}
274
274
  {{/vendorExtensions.x-alpha-custom-function}}
275
275
  {{/vendorExtensions.x-alpha-service-name}}
276
276
  {{#vendorExtensions.x-alpha-service-name}}
@@ -302,7 +302,7 @@ def {{operationId}}(
302
302
  cls={{vendorExtensions.x-alpha-response-factory}}
303
303
  )
304
304
  {{/vendorExtensions.x-alpha-response-factory}}
305
- response_object, status_code = create_response_object(
305
+ return response_object_function(
306
306
  http_codes=http_codes,
307
307
  status_code={{code}},
308
308
  status_message='{{message}}',
@@ -310,10 +310,9 @@ def {{operationId}}(
310
310
  {{#vendorExtensions.x-content-type}}data_type='{{vendorExtensions.x-content-type}}'{{/vendorExtensions.x-content-type}}
311
311
  {{#vendorExtensions.x-alpha-cookie-support}}response_type='flask'{{/vendorExtensions.x-alpha-cookie-support}}
312
312
  )
313
- return response_object, status_code
314
313
  {{/returnType}}
315
314
  {{^returnType}}
316
- return create_response_object(
315
+ return response_object_function(
317
316
  http_codes=http_codes,
318
317
  status_code=204,
319
318
  status_message='{{message}}',
@@ -326,7 +325,7 @@ def {{operationId}}(
326
325
  {{#is4xx}}
327
326
  {{#vendorExtensions.x-alpha-exception}}
328
327
  except {{vendorExtensions.x-alpha-exception}} as exc:
329
- return create_response_object(
328
+ return response_object_function(
330
329
  http_codes=http_codes,
331
330
  status_code={{code}},
332
331
  status_message=f'{exc}'
@@ -336,7 +335,7 @@ def {{operationId}}(
336
335
  {{#is5xx}}
337
336
  {{#vendorExtensions.x-alpha-exception}}
338
337
  except {{vendorExtensions.x-alpha-exception}} as exc:
339
- return create_response_object(
338
+ return response_object_function(
340
339
  http_codes=http_codes,
341
340
  status_code={{code}},
342
341
  status_message=f'{exc}'
@@ -345,92 +344,92 @@ def {{operationId}}(
345
344
  {{/is5xx}}
346
345
  {{/responses}}
347
346
  except BadRequestException as exc:
348
- return create_response_object(
347
+ return response_object_function(
349
348
  http_codes=http_codes,
350
349
  status_code=400,
351
350
  status_message=f'{exc}'
352
351
  )
353
352
  except UnauthorizedException as exc:
354
- return create_response_object(
353
+ return response_object_function(
355
354
  http_codes=http_codes,
356
355
  status_code=401,
357
356
  status_message=f'{exc}'
358
357
  )
359
358
  except ForbiddenException as exc:
360
- return create_response_object(
359
+ return response_object_function(
361
360
  http_codes=http_codes,
362
361
  status_code=403,
363
362
  status_message=f'{exc}'
364
363
  )
365
364
  except NotFoundException as exc:
366
- return create_response_object(
365
+ return response_object_function(
367
366
  http_codes=http_codes,
368
367
  status_code=404,
369
368
  status_message=f'{exc}'
370
369
  )
371
370
  except MethodNotAllowedException as exc:
372
- return create_response_object(
371
+ return response_object_function(
373
372
  http_codes=http_codes,
374
373
  status_code=405,
375
374
  status_message=f'{exc}'
376
375
  )
377
376
  except NotAcceptableException as exc:
378
- return create_response_object(
377
+ return response_object_function(
379
378
  http_codes=http_codes,
380
379
  status_code=406,
381
380
  status_message=f'{exc}'
382
381
  )
383
382
  except ConflictException as exc:
384
- return create_response_object(
383
+ return response_object_function(
385
384
  http_codes=http_codes,
386
385
  status_code=409,
387
386
  status_message=f'{exc}'
388
387
  )
389
388
  except PayloadTooLargeException as exc:
390
- return create_response_object(
389
+ return response_object_function(
391
390
  http_codes=http_codes,
392
391
  status_code=413,
393
392
  status_message=f'{exc}'
394
393
  )
395
394
  except UnprocessableContentException as exc:
396
- return create_response_object(
395
+ return response_object_function(
397
396
  http_codes=http_codes,
398
397
  status_code=422,
399
398
  status_message=f'{exc}'
400
399
  )
401
400
  except NotImplementedException as exc:
402
- return create_response_object(
401
+ return response_object_function(
403
402
  http_codes=http_codes,
404
403
  status_code=501,
405
404
  status_message=f'{exc}'
406
405
  )
407
406
  except BadGatewayException as exc:
408
- return create_response_object(
407
+ return response_object_function(
409
408
  http_codes=http_codes,
410
409
  status_code=502,
411
410
  status_message=f'{exc}'
412
411
  )
413
412
  except ServiceUnavailableException as exc:
414
- return create_response_object(
413
+ return response_object_function(
415
414
  http_codes=http_codes,
416
415
  status_code=503,
417
416
  status_message=f'{exc}'
418
417
  )
419
418
  except GatewayTimeoutException as exc:
420
- return create_response_object(
419
+ return response_object_function(
421
420
  http_codes=http_codes,
422
421
  status_code=504,
423
422
  status_message=f'{exc}'
424
423
  )
425
424
  except ServerErrorException as exc:
426
- return create_response_object(
425
+ return response_object_function(
427
426
  http_codes=http_codes,
428
427
  status_code=500,
429
428
  status_message=f'{exc}'
430
429
  )
431
430
  except Exception as exc:
432
431
  traceback.print_exc()
433
- return create_response_object(
432
+ return response_object_function(
434
433
  http_codes=http_codes,
435
434
  status_code=500,
436
435
  status_message=f'{exc}'
alpha/infra/__init__.py CHANGED
@@ -3,7 +3,7 @@ from alpha.infra.connectors.oidc_connector import (
3
3
  KeyCloakOIDCConnector,
4
4
  )
5
5
  from alpha.infra.connectors.sql_alchemy import SqlAlchemyDatabase
6
- from alpha.infra.models.filter_operators import And, Or
6
+ from alpha.infra.models.filter_operators import And, Or, FilterOperator
7
7
  from alpha.infra.models.json_patch import JsonPatch
8
8
  from alpha.infra.models.order_by import OrderBy, Order
9
9
  from alpha.infra.models.search_filter import SearchFilter, Operator
@@ -22,6 +22,7 @@ __all__ = [
22
22
  "SqlAlchemyDatabase",
23
23
  "And",
24
24
  "Or",
25
+ "FilterOperator",
25
26
  "JsonPatch",
26
27
  "OrderBy",
27
28
  "Order",
@@ -4,7 +4,7 @@
4
4
  - Or
5
5
  """
6
6
 
7
- from typing import Any, Callable, Iterable
7
+ from typing import Any, Callable, Iterable, Self
8
8
 
9
9
  from sqlalchemy.orm import Query
10
10
  from sqlalchemy.sql.expression import ColumnElement, and_, or_
@@ -17,13 +17,11 @@ class FilterOperator:
17
17
  search query
18
18
  """
19
19
 
20
- def __init__(self, *search_filters: SearchFilter):
20
+ def __init__(self, *search_filters: SearchFilter | Self) -> None:
21
21
  """Instantiate the filter operator by storing the search filter
22
22
  objects
23
23
  """
24
- self.search_filters: Iterable[SearchFilter | FilterOperator] = (
25
- search_filters
26
- )
24
+ self.search_filters: Iterable[SearchFilter | Self] = search_filters
27
25
 
28
26
  @property
29
27
  def filter_operator(
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  from contextlib import suppress
3
3
 
4
- import jsonpatch
4
+ import jsonpatch # type: ignore[import-untyped]
5
5
 
6
6
 
7
7
  class JsonPatch(jsonpatch.JsonPatch):
@@ -60,15 +60,17 @@ class OrderBy(QueryClause):
60
60
  determines the appropriate subclass based on the order attribute.
61
61
  """
62
62
  super().__post_init__()
63
- self.__class__ = self._get_filter_class()
63
+ self.__class__ = self._get_filter_class() # type: ignore
64
64
 
65
- def _get_filter_class(self) -> object:
65
+ def _get_filter_class(self) -> type["OrderBy"]:
66
66
  """Determine the appropriate subclass based on the order attribute."""
67
67
  match self.order:
68
68
  case Order.ASC:
69
69
  return AscendingOrder
70
70
  case Order.DESC:
71
71
  return DescendingOrder
72
+ case _:
73
+ return OrderBy
72
74
 
73
75
 
74
76
  class AscendingOrder(OrderBy):
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any
2
+ from typing import Any, NoReturn
3
3
 
4
4
  from sqlalchemy.orm.attributes import InstrumentedAttribute
5
5
  from sqlalchemy.orm.query import Query
@@ -58,7 +58,7 @@ class QueryClause:
58
58
  self.field, # type: ignore
59
59
  )
60
60
 
61
- def _raise_instrumented_attr_exception(self):
61
+ def _raise_instrumented_attr_exception(self) -> NoReturn:
62
62
  """Raise an exception indicating that the instrumented attribute is
63
63
  missing."""
64
64
  raise exceptions.InstrumentedAttributeMissing(
@@ -32,5 +32,3 @@ class SqlDatabase(Protocol):
32
32
  def drop_tables(
33
33
  self, metadata: sa.MetaData, tables: list[sa.Table] | None = None
34
34
  ) -> None: ...
35
-
36
- def _create_schema(self, engine: Engine, schema_name: str) -> None: ...
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import Protocol, runtime_checkable
2
+ from typing import Any, Protocol, runtime_checkable
3
3
 
4
4
 
5
5
  @runtime_checkable
@@ -33,7 +33,9 @@ class TokenFactory(Protocol):
33
33
  """
34
34
  ...
35
35
 
36
- def validate(self, token: str) -> bool:
36
+ def validate(
37
+ self, token: str, options: dict[str, Any] | None = None
38
+ ) -> bool:
37
39
  """Validate an authentication token.
38
40
 
39
41
  Parameters
@@ -49,7 +51,7 @@ class TokenFactory(Protocol):
49
51
  ...
50
52
 
51
53
  def get_payload(
52
- self, token: str, options: dict[str, bool] | None
54
+ self, token: str, options: dict[str, bool] | None = None
53
55
  ) -> dict[str, str]:
54
56
  """Retrieve the payload from an authentication token.
55
57
 
@@ -267,7 +267,7 @@ class Identity:
267
267
  """
268
268
  subject = str(user.id) if user.id else user.username
269
269
  return cls(
270
- subject=subject,
270
+ subject=subject, # type: ignore
271
271
  username=user.username,
272
272
  email=user.email,
273
273
  display_name=user.display_name,
@@ -275,7 +275,7 @@ class Identity:
275
275
  permissions=user.permissions or [],
276
276
  claims={},
277
277
  issued_at=datetime.now(tz=timezone.utc),
278
- role=user.role,
278
+ role=user.role, # type: ignore
279
279
  admin=user.admin,
280
280
  )
281
281
 
@@ -320,7 +320,7 @@ class Identity:
320
320
  )
321
321
  for group in user.groups or []:
322
322
  self.groups = self._append_on_sequence(self.groups, group)
323
- self.role = user.role
323
+ self.role = user.role # type: ignore
324
324
  self.admin = user.admin
325
325
 
326
326
  def update_from_groups(self, groups: list[Group]) -> None:
@@ -457,8 +457,8 @@ class Identity:
457
457
  return groups
458
458
 
459
459
  def _append_on_sequence(
460
- self, sequence: Sequence[str], item: str
461
- ) -> Sequence[str]:
460
+ self, sequence: Sequence[Any], item: Any
461
+ ) -> Sequence[Any]:
462
462
  """Helper method to append an item to a sequence if it's not already
463
463
  present.
464
464
 
@@ -1,7 +1,5 @@
1
1
  """OIDC identity providers."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from datetime import datetime, timezone
6
4
  from typing import Any, Mapping, Sequence, cast
7
5
 
@@ -379,12 +379,12 @@ class AuthenticationService:
379
379
  "Logout successful",
380
380
  )
381
381
 
382
- def verify(self, token: str) -> Identity:
383
- """Verify a token and return the associated identity.
382
+ def verify(self, auth_token: str) -> Identity:
383
+ """Verify an auth_token and return the associated identity.
384
384
 
385
385
  Parameters
386
386
  ----------
387
- token
387
+ auth_token
388
388
  Authentication token.
389
389
 
390
390
  Returns
@@ -392,7 +392,7 @@ class AuthenticationService:
392
392
  Identity
393
393
  Verified Identity instance.
394
394
  """
395
- return self._identity_provider.validate(Token(value=token))
395
+ return self._identity_provider.validate(Token(value=auth_token))
396
396
 
397
397
  def refresh_token(
398
398
  self, refresh_token: str, auth_token: str | None = None
@@ -443,6 +443,12 @@ class AuthenticationService:
443
443
  identity = self._identity_provider.get_user(
444
444
  subject=stored_refresh_token.subject
445
445
  )
446
+ # If configured to merge with database users and groups, perform
447
+ # the merge operations on the identity.
448
+ if self._merge_with_database_users:
449
+ identity = self._merge_identity_with_user(identity)
450
+ if self._merge_with_database_groups:
451
+ identity = self._merge_identity_with_groups(identity)
446
452
 
447
453
  # If an auth token is provided and the identity could not be retrieved
448
454
  # using the refresh token, attempt to retrieve the identity from the
@@ -4,7 +4,7 @@ import logging
4
4
  from logging.config import dictConfig
5
5
  from typing import Any
6
6
 
7
- from gunicorn import glogging
7
+ from gunicorn import glogging # type: ignore[import-untyped]
8
8
 
9
9
  from alpha.factories.logging_handler_factory import LoggingHandlerFactory
10
10
 
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+
4
+ def custom_response_builder(
5
+ status_code: int,
6
+ **kwargs: Any,
7
+ ) -> tuple[dict[str, Any], int]:
8
+ """Custom response builder for testing purposes."""
9
+ return {
10
+ "status_code": status_code,
11
+ **kwargs,
12
+ }, status_code
@@ -96,12 +96,6 @@ class TestService:
96
96
  def is_list(self, obj: Any) -> bool:
97
97
  return isinstance(obj, list)
98
98
 
99
- # def return_object(self, obj: Any) -> Any:
100
- # return obj
101
-
102
- # def count_unique_items(self, obj: Any) -> int:
103
- # return len(set(obj))
104
-
105
99
  def check_dataclass(self, pet: Pet) -> Pet:
106
100
  if isinstance(pet, Pet):
107
101
  return pet
@@ -57,6 +57,10 @@ def create_response_object(
57
57
  ) -> tuple[Response, int] | tuple[dict[str, Any], int]:
58
58
  """Create a HTTP response object.
59
59
 
60
+ The response object can be either a dictionary or a Flask Response object,
61
+ depending on the value of `response_type`. The response will include the
62
+ status code, a human-readable message, and optionally additional data.
63
+
60
64
  Parameters
61
65
  ----------
62
66
  status_code
@@ -79,7 +83,12 @@ def create_response_object(
79
83
 
80
84
  Returns
81
85
  -------
82
- A tuple containing the response object and the HTTP status code.
86
+ tuple[dict[str, Any], int]
87
+ A tuple containing the response object as a dictionary and the HTTP
88
+ status code. When response_type is "dict".
89
+ tuple[Response, int]
90
+ A tuple containing the flask.Response object and the HTTP status code.
91
+ When response_type is "flask".
83
92
  """
84
93
  if response_type is None:
85
94
  response_type = "dict"
@@ -35,22 +35,22 @@ def verify_identity(
35
35
  if isinstance(identity, Identity):
36
36
  identity = identity.to_dict()
37
37
 
38
- identity_subject = identity.get("subject")
39
- identity_role = identity.get("role")
40
- identity_permissions = identity.get("permissions")
41
- identity_groups = identity.get("groups")
38
+ identity_subject: str | None = identity.get("subject")
39
+ identity_role: str | None = identity.get("role")
40
+ identity_permissions: list[str] = identity.get("permissions", [])
41
+ identity_groups: list[str] = identity.get("groups", [])
42
42
 
43
43
  # Verify if identity role is present in required roles
44
44
  if roles and identity_role not in roles:
45
45
  raise InsufficientPermissionsException(
46
- f"Role \'{identity_role}\' of \'{identity_subject}\' is not "
46
+ f"Role '{identity_role}' of '{identity_subject}' is not "
47
47
  f"sufficient. Required roles: {roles}"
48
48
  )
49
49
 
50
50
  # Verify if identity groups intersect with required groups
51
51
  if groups and not set(identity_groups).intersection(set(groups)):
52
52
  raise InsufficientPermissionsException(
53
- f"Groups \'{identity_groups}\' of \'{identity_subject}\' do not "
53
+ f"Groups '{identity_groups}' of '{identity_subject}' do not "
54
54
  f"intersect with required groups: {groups}"
55
55
  )
56
56
 
@@ -59,6 +59,6 @@ def verify_identity(
59
59
  set(identity_permissions)
60
60
  ):
61
61
  raise InsufficientPermissionsException(
62
- f"Permissions \'{identity_permissions}\' of \'{identity_subject}\'"
62
+ f"Permissions '{identity_permissions}' of '{identity_subject}'"
63
63
  f" do not include required permissions: {permissions}"
64
64
  )
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: alpha-python
3
+ Version: 0.6.2
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
+ Author-email: Bart Reijling <bart@reijling.eu>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/breijling/alpha
8
+ Project-URL: Issues, https://github.com/breijling/alpha/issues
9
+ Project-URL: Documentation, https://alpha-python.readthedocs.io/en/latest/
10
+ Project-URL: Changelog, https://alpha-python.readthedocs.io/en/latest/changelog/
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: <3.15,>=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: attrs>=25.4.0
23
+ Requires-Dist: gunicorn>=23.0.0
24
+ Requires-Dist: jsonpatch>=1.33
25
+ Requires-Dist: numpy>=2.3.5
26
+ Requires-Dist: pandas>=1.13
27
+ Requires-Dist: pydantic>=2.12.5
28
+ Requires-Dist: pyjwt>=2.10.1
29
+ Requires-Dist: six>=1.17.0
30
+ Requires-Dist: sqlalchemy>=2.0.44
31
+ Requires-Dist: requests>=2.28.1
32
+ Requires-Dist: dependency-injector[yaml]!=4.48.3,<5.0.0,>=4.42.0
33
+ Requires-Dist: argon2-cffi>=25.1.0
34
+ Requires-Dist: httpx
35
+ Requires-Dist: cryptography
36
+ Provides-Extra: api-generator
37
+ Requires-Dist: openapi-generator-cli==7.14.0; extra == "api-generator"
38
+ Requires-Dist: jdk4py; extra == "api-generator"
39
+ Provides-Extra: flask
40
+ Requires-Dist: connexion[swagger-ui]<3,>=2.14.2; extra == "flask"
41
+ Requires-Dist: swagger-ui-bundle>=0.0.2; extra == "flask"
42
+ Requires-Dist: python_dateutil>=2.6.0; extra == "flask"
43
+ Requires-Dist: itsdangerous==1.1.0; extra == "flask"
44
+ Requires-Dist: MarkupSafe<2.0.2; extra == "flask"
45
+ Requires-Dist: Jinja2==2.11.2; extra == "flask"
46
+ Requires-Dist: Flask<2,>=1.1.2; extra == "flask"
47
+ Requires-Dist: Werkzeug<2.1.0,>=1.0.1; extra == "flask"
48
+ Requires-Dist: flask-cors>=3.0.10; extra == "flask"
49
+ Requires-Dist: Flask-Compress>=1.13; extra == "flask"
50
+ Provides-Extra: mysql
51
+ Requires-Dist: pymysql>=1.1.2; extra == "mysql"
52
+ Provides-Extra: postgresql
53
+ Requires-Dist: psycopg2-binary>=2.9.11; extra == "postgresql"
54
+ Provides-Extra: ldap
55
+ Requires-Dist: ldap3>=2.9.1; extra == "ldap"
56
+ Dynamic: license-file
57
+
58
+ # Alpha
59
+
60
+ 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.
61
+
62
+ ## Badges
63
+
64
+ [![PyPI version](https://badge.fury.io/py/alpha-python.svg?icon=si%3Apython)](https://badge.fury.io/py/alpha-python)
65
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/alpha-python.svg?label=PyPI%20downloads)](https://pypistats.org/packages/alpha-python)
66
+ [![Build Status](https://github.com/breijling/alpha/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/breijling/alpha/actions/workflows/python-app.yml)
67
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/alpha-python.svg?color=%2334D058)](https://pypi.org/project/alpha-python)
68
+ [![Coverage Status](https://coveralls.io/repos/github/BReijling/alpha/badge.svg?branch=main)](https://coveralls.io/github/BReijling/alpha?branch=main)
69
+ [![uv](https://img.shields.io/badge/package%20manager-uv-5C4EE5)](https://docs.astral.sh/uv/)
70
+ [![mypy](https://img.shields.io/badge/type%20check-mypy-2A6DB2)](https://mypy-lang.org/)
71
+ [![Pytest](https://img.shields.io/badge/testing-pytest-0A9EDC)](https://docs.pytest.org/)
72
+ [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/)
73
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
74
+ [![Documentation Status](https://readthedocs.org/projects/alpha-python/badge/?version=latest)](https://alpha-python.readthedocs.io/en/latest/)
75
+ [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
76
+ [![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-pink?logo=github)](https://github.com/sponsors/BReijling)
77
+
78
+ ## TL;DR
79
+
80
+ Alpha provides a comprehensive set of tools for building Python applications that interact with APIs and databases, including API code generation, authentication and authorization, database access layers, error handling, logging, and more. It is designed to be the first dependency you add to your project, providing a solid foundation for your application's architecture.
81
+
82
+ ## Documentation
83
+
84
+ Full documentation is available at [alpha-python.readthedocs.io](https://alpha-python.readthedocs.io/).
85
+
86
+ ## Installation
87
+
88
+ The library is still in development, but you can already install it using pip:
89
+
90
+ ```shell
91
+ pip install alpha-python
92
+ ```
93
+
94
+ If you want to use the alpha cli for generating API code, you can install it using pip as well:
95
+
96
+ ```shell
97
+ pip install alpha-python[api-generator]
98
+ ```
99
+
100
+ If you want to add the library to your API project, you can add it to your pyproject.toml file:
101
+
102
+ ```shell
103
+ # Poetry example
104
+ poetry add alpha-python --extras "flask, postgresql"
105
+ poetry add --dev alpha-python --extras "api-generator"
106
+
107
+ # UV example
108
+ uv add alpha-python --extra flask --extra postgresql
109
+ uv add --dev alpha-python --extra api-generator
110
+ ```
111
+
112
+ ## Features
113
+
114
+ - API code generation
115
+ - Authentication and authorization
116
+ - Database interaction
117
+ - Logging
118
+ - Error handling
119
+ - And much more!
120
+
121
+ ## Usage
122
+
123
+ The library contains many components. Below are a few practical examples that map to the guides in the documentation.
124
+
125
+ ### 1) API code generation using OpenAPI spec
126
+
127
+ ```shell
128
+ alpha api gen --spec-file specification/openapi.yaml --service-package my_app
129
+
130
+ alpha api run --port 8080
131
+ ```
132
+
133
+ ### 2) Authenticate with Keycloak (OIDC)
134
+
135
+ ```python
136
+ from alpha import KeyCloakOIDCConnector, KeyCloakProvider, PasswordCredentials
137
+
138
+ keycloak_connector = KeyCloakOIDCConnector(
139
+ base_url="https://keycloak.example.com",
140
+ realm="myrealm",
141
+ client_id="myclient",
142
+ client_secret="myclientsecret",
143
+ )
144
+
145
+ keycloak_provider = KeyCloakProvider(connector=keycloak_connector)
146
+
147
+ credentials = PasswordCredentials(username="user1", password="user1_password")
148
+ identity = keycloak_provider.authenticate(credentials)
149
+ ```
150
+
151
+ ### 3) Query data using SqlAlchemyDatabase + SqlAlchemyRepository
152
+
153
+ ```python
154
+ from alpha import SqlAlchemyDatabase, SqlAlchemyRepository
155
+ from my_app import User
156
+
157
+ db = SqlAlchemyDatabase(conn_str="postgresql://user:password@localhost:5432/mydatabase")
158
+
159
+ with db.get_session() as session:
160
+ users = SqlAlchemyRepository[User](session=session, default_model=User)
161
+ user = users.get_by_id(1)
162
+ ```
163
+
164
+ ### 4) Use Unit of Work for transactional operations
165
+
166
+ ```python
167
+ from alpha import (
168
+ RepositoryModel,
169
+ SqlAlchemyDatabase,
170
+ SqlAlchemyRepository,
171
+ SqlAlchemyUnitOfWork,
172
+ )
173
+ from my_app import User, OrmMapper
174
+
175
+ db = SqlAlchemyDatabase(..., mapper=OrmMapper)
176
+ repositories = [
177
+ RepositoryModel(
178
+ name="users",
179
+ repository=SqlAlchemyRepository[User],
180
+ default_model=User,
181
+ )
182
+ ]
183
+
184
+ uow = SqlAlchemyUnitOfWork(db=db, repos=repositories)
185
+
186
+ with uow:
187
+ user = uow.users.get_by_id(1)
188
+ ```
189
+
190
+ See also:
191
+
192
+ - Design principles and patterns: https://alpha-python.readthedocs.io/en/latest/concepts/design-principles/
193
+ - Dependency injection concept: https://alpha-python.readthedocs.io/en/latest/concepts/dependency-injection/
194
+ - API code generation guide: https://alpha-python.readthedocs.io/en/latest/guides/api-generation/
195
+ - Authentication guide: https://alpha-python.readthedocs.io/en/latest/guides/authentication/
196
+ - Database interaction guide: https://alpha-python.readthedocs.io/en/latest/guides/database-interaction/
197
+
198
+ ## Contributing
199
+
200
+ If you want to contribute to the development of this library, you can fork the repository and create a pull request with your changes.
201
+
202
+ ## Support
203
+
204
+ If Alpha saves you time in production, consider supporting development by buying me a coffee! Your support helps me to continue improving the library and adding new features. Thank you!
205
+
206
+ <a href="https://www.buymeacoffee.com/breijling"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=breijling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" /></a>
207
+
208
+ ## License
209
+
210
+ This library is licensed under the MIT License. See the LICENSE file for more information.
@@ -1,6 +1,6 @@
1
- alpha/__init__.py,sha256=tktKTmPjYOf91PvLdmlczAyjguUt9UnSdN8qGBU4npw,6115
2
- alpha/cli.py,sha256=YdBvz_nqvC1c4kA-Xb1KtS-1Or6rbDu2VcF3ClatP6o,5657
3
- alpha/encoder.py,sha256=g06nYgwvFK37M8QtjvURBDj-eaNiOobfSGQealwWBdY,2513
1
+ alpha/__init__.py,sha256=FnXcEv9PSCLWDKE4-9GCqI7HSAvzo3WW_mfcBLPBBxo,6153
2
+ alpha/cli.py,sha256=YTWM7lzmydYazXMJ6LULywvJTMHzvfTO6yNuPrUgHCY,5813
3
+ alpha/encoder.py,sha256=sNYZ30uNiJWV0xpeIiTVCDAsM86Nk_914kF7Ye2Woao,2610
4
4
  alpha/exceptions.py,sha256=AHoFMPyHvjj6j_1X2TS40dSaFBzCDtgzAmucimSZjfc,5793
5
5
  alpha/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  alpha/adapters/__init__.py,sha256=pGpAtLuVs-jOyRShjO1BwSHpB5MyStJ3Hs9wPm49Q2s,201
@@ -21,17 +21,17 @@ alpha/factories/_type_mapping.py,sha256=f8cRfu8KUfw1ggY0Txs6fEX2e6GaXrsNcc2SCeYF
21
21
  alpha/factories/class_factories.py,sha256=e0NCQDU1qCOQxoApICjZi3dnLLZ85Py5JOHqzLRbOSQ,17131
22
22
  alpha/factories/default_field_factory.py,sha256=J8fM48Yar1KpXX8SR1iB3buGr1hkYeayH5IYWoBqaHk,1770
23
23
  alpha/factories/field_iterator.py,sha256=q4B1bUuY1ZCWdS0F5mRGfaYcbKM-WlPUNixRzk6yAS8,6305
24
- alpha/factories/jwt_factory.py,sha256=7FtX3rtb21xUy2_rhD1KwyGxOBX6eWXXVreVe15hD5E,8238
24
+ alpha/factories/jwt_factory.py,sha256=ZuamKmi12uZve-TQByppeMlH1aiBNWraejd2wpZuB3k,8429
25
25
  alpha/factories/logging_handler_factory.py,sha256=ZoVkD2S3Pl7NMMfhcFF8k76BlKTsvis75udLX-JqQ30,3063
26
26
  alpha/factories/model_class_factory.py,sha256=VK80vLHGF-XdlW3aln5C9tHSQv81NLxz5ti5jYxdv4w,5862
27
- alpha/factories/password_factory.py,sha256=rSdM57SMNRCU1QxYTgKHWEcJRH6Lp6toSqOXoS0om_s,4618
27
+ alpha/factories/password_factory.py,sha256=uK8X5bNvlyUtxvJXH7MXmYLiaM4jmsS6sZrx7Ew07kM,5031
28
28
  alpha/factories/request_factory.py,sha256=vjkvsBkKKUZ7me5a8cEcFYet03qnUZ6f5Qyktfxospw,7159
29
29
  alpha/factories/response_factory.py,sha256=mmHRWa7TRjZMuIPojI69At1Q21bBa-qiH9LLtKngGM8,6497
30
30
  alpha/factories/type_factories.py,sha256=3MCSQ1g1neBVPk0cSbhmGED7O4AQNMmBK8OmTLwuaGM,6478
31
31
  alpha/factories/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  alpha/factories/models/factory_classes.py,sha256=RpmJdPmC5MUNTD4TK6TaqBzd2ezLGSjsHGltl-_6tqo,1131
33
33
  alpha/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- alpha/handlers/api_generate_handler.py,sha256=BEL_NWUTfIfAHpM4rTTyutl_ruecbfrFjTcsRiP2q-w,7320
34
+ alpha/handlers/api_generate_handler.py,sha256=AV8-vnhrkn1omsNQ3RTAFO_wfQ6j5lsgle97AZ-m4RY,7484
35
35
  alpha/handlers/api_run_handler.py,sha256=wuYCJCFuFpKeX9Jqu6vi4rVK8Bm6VmI9FNHny_PrKNA,1414
36
36
  alpha/handlers/base_handler.py,sha256=iBNFhG0d-Si02uJashxmD7tHj16kaYPGP8i2RPljK30,523
37
37
  alpha/handlers/gen-code.sh,sha256=vhWfFzooKxgrSzR9cYXg0UKe4aT9eG8ZiaAasjTPXnY,1066
@@ -46,9 +46,9 @@ alpha/handlers/templates/python-flask/Dockerfile.mustache,sha256=nyginFsU-nKyq0b
46
46
  alpha/handlers/templates/python-flask/README.mustache,sha256=X-giJMN_juUrVoW2mj_2Npw6q1s8NGsAgToK2um3NLQ,1368
47
47
  alpha/handlers/templates/python-flask/__init__model.mustache,sha256=JRwyJmOCAD8nskG1DnYjt7orBTmza8qPQ2N24JaPcuY,209
48
48
  alpha/handlers/templates/python-flask/__init__test.mustache,sha256=wUxDXcyE4rPh3sDp8MvR5m8vqzoU25Fw1bo_xh6InnY,438
49
- alpha/handlers/templates/python-flask/__main__.mustache,sha256=GetXHmF7BXxrwVajfkI6OjLQV7ekFxD9SPYHjZLNNWY,2393
49
+ alpha/handlers/templates/python-flask/__main__.mustache,sha256=D11Z_bhka_NsK3EP8iSzV8_Hcc15L-uOntH2IbzrKng,2942
50
50
  alpha/handlers/templates/python-flask/base_model.mustache,sha256=B--jl9LmfiHACq9n1kDxm6YTi6tb1oSUFnZya_IKOhU,2184
51
- alpha/handlers/templates/python-flask/controller.mustache,sha256=XQPbgy4VkzzNlRsj4z8uE0RZB2D7BR3au4wGvu_lvCU,17231
51
+ alpha/handlers/templates/python-flask/controller.mustache,sha256=iOIpFCFVj0y_fvKqY8quwTR4Cdsk0TaKZuOCKojUfOE,17341
52
52
  alpha/handlers/templates/python-flask/controller_test.mustache,sha256=2i2cwgQonT1RMJQwdxeq8B3x8-hLjpK7YkI--LkKLOI,2848
53
53
  alpha/handlers/templates/python-flask/dockerignore.mustache,sha256=hqlHuqIMLuuwvVqK6xhIcfaezKoChriR3D7TvxtkjGo,906
54
54
  alpha/handlers/templates/python-flask/encoder.mustache,sha256=03rsvhN_QdqaWsQC2v3772UvrLEXna9WcvxQsl6mwiU,866
@@ -65,7 +65,7 @@ alpha/handlers/templates/python-flask/tox.mustache,sha256=iuEc1JquvGZ0UlCCJus2X_
65
65
  alpha/handlers/templates/python-flask/travis.mustache,sha256=M9UNXQnfg8F6uFvOSsay8FQ0YvPDi8EF6HIiu4UW4-o,319
66
66
  alpha/handlers/templates/python-flask/typing_utils.mustache,sha256=mGDTJkj9V9w7NsFSljw33GKB-gWfqoceAot-iHTViNs,809
67
67
  alpha/handlers/templates/python-flask/util.mustache,sha256=iQVhsAN_ZCQuQGOxoqATTE90fkIEojKyrfooFqa7Jck,3534
68
- alpha/infra/__init__.py,sha256=M52HIbeT0yvgwtU2yEPpOpF12p2dxXfCBfm_Fj796mU,980
68
+ alpha/infra/__init__.py,sha256=4eIKsW1N06_vVBrO2u1ESU5iURBb0cGvxcscaPsB4PE,1018
69
69
  alpha/infra/connectors/__init__.py,sha256=kEh_mOIIqkWeY4cilislM8ZjKXNFp8zBuMThfbJ1CGc,647
70
70
  alpha/infra/connectors/ldap_connector.py,sha256=ntBheImY90qtZ8tTYv0Dh64vxNho0k8FwTOAYkVdZsU,4019
71
71
  alpha/infra/connectors/oidc_connector.py,sha256=z9AY1lMVK5nULd80zOvJzo8zPbp6CHJ7fv2smp71eIs,15675
@@ -73,10 +73,10 @@ alpha/infra/connectors/sql_alchemy.py,sha256=8DivUj25_OAVaXmaCsbSnHgOD1w5_ewAXSl
73
73
  alpha/infra/databases/__init__.py,sha256=IyA_aB9h_7rOM-aa50QLsA_q4e9W4HnJOqh76aDyrj4,106
74
74
  alpha/infra/databases/sql_alchemy.py,sha256=7t17M8UEgW1NMX9LYW6A14EM5UxEJZyp-8a_xLEONAs,235
75
75
  alpha/infra/models/__init__.py,sha256=SIakw9bAPXwFmx3I7exCRBs3e2Ug3etOJy6JyIOR1Uc,348
76
- alpha/infra/models/filter_operators.py,sha256=SbI5jlFYOdoNPfIYYKvvhesAENipWj2Zlh31g4e6EKQ,2812
77
- alpha/infra/models/json_patch.py,sha256=OVThIWzg4n0SixHqoU5UQkLdHvu2t1IgL2gjDBYSvMc,1721
78
- alpha/infra/models/order_by.py,sha256=HY3eRwHNW2JflavKvHpDiQKJuC5rY9HU5cuXPywR560,4493
79
- alpha/infra/models/query_clause.py,sha256=R7CWIin99_0XckhyVKK4qwNnOV-QtWwMceMveVxq63w,2533
76
+ alpha/infra/models/filter_operators.py,sha256=LJw2w8v1FvDhQnEZ8hKq3Rq54_MwolGn7SSDT5t5rT4,2797
77
+ alpha/infra/models/json_patch.py,sha256=w8L59E6w-aSeEknww-JLCrB1kEpFbPXcRlweKP4kU1A,1753
78
+ alpha/infra/models/order_by.py,sha256=QN4ALsbJ-zoh1dx5OHZ2L9pijwfjBM5G0pBbpNrNHnM,4569
79
+ alpha/infra/models/query_clause.py,sha256=xLWxm-cWrODMqpqqVIu1veSVBWLENt97TTHq8CinP7M,2555
80
80
  alpha/infra/models/search_filter.py,sha256=5Q0lbeAYOnyzudX7HfEwZSzLKC9oJgT62jnHaNrRGmg,21755
81
81
  alpha/interfaces/__init__.py,sha256=XGCncGbfzjkGEq8aN7_2m9vZyCfcNVUe8IhMj8krbzk,1441
82
82
  alpha/interfaces/api_repository.py,sha256=ysnzKi_cBdbzGBL_GVdEGftp_gbl8wb4odjTUKOAaN4,21836
@@ -88,10 +88,10 @@ alpha/interfaces/openapi_model.py,sha256=9QayfwbwTg_3-reFso_ktwNqGI2f2C4AQOAPvPH
88
88
  alpha/interfaces/patchable.py,sha256=9L42VdrvfDhKKlfjQAMDX6FDA0R-U6QkOWlAHUdxneQ,705
89
89
  alpha/interfaces/providers.py,sha256=S0uBThEI_qqRpxyQmwNNSPGMcAVIU2ngXIimflDqUbc,4162
90
90
  alpha/interfaces/pydantic_instance.py,sha256=S96jLEWRmDyvYV3VqofSqjiN-lcjfkjiNp7Vpumw49A,245
91
- alpha/interfaces/sql_database.py,sha256=H52NR1ti2j9G9zJsibPtKmXL7WdHOzeSyPXO4Zq9wlA,996
91
+ alpha/interfaces/sql_database.py,sha256=-YiMBSrGkd2NTS4FEBjXenlPnVQW2t3SvQJMDRyHDjA,919
92
92
  alpha/interfaces/sql_mapper.py,sha256=OSGIwbtlQ5-cBHUIiJfLgJlkBU3MgMAbSdxj8X2v-eI,624
93
93
  alpha/interfaces/sql_repository.py,sha256=nJCdF_G8YS7_VST_OL14wBA_Ne_DyGZhQQDAta1zqYk,9814
94
- alpha/interfaces/token_factory.py,sha256=d0_Jbj6rA8tI0O2rIp5vZzyZKI47B-sDQNMAW_zCgCs,1817
94
+ alpha/interfaces/token_factory.py,sha256=MeSvnyH699vBG0c7Oli4M6RT1wEsCVTNy7V4XQpu31o,1882
95
95
  alpha/interfaces/unit_of_work.py,sha256=P32wTGw4N54QV_x2BBusrApANmI88wgA7bLmC-1AozI,1464
96
96
  alpha/interfaces/updatable.py,sha256=vGpxvqrn9dCMumP_Ty3EGs-bp3KiPG8l-v-9rfjOLSQ,472
97
97
  alpha/mixins/__init__.py,sha256=aPTNpi4JEzdsFW9Joq0ktgtoP2MJqNLOc1uRPxJkl4A,252
@@ -102,10 +102,10 @@ alpha/providers/__init__.py,sha256=DM0Zu_hYNc9JI7cKVQD7J84lwpv5-GNTntMzM1k_ZPI,9
102
102
  alpha/providers/api_key_provider.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
103
  alpha/providers/database_provider.py,sha256=Q0qAdJYgEg1hjVHUenB-Qy-pW69E5p0cZ5IMP_JWE9Q,7974
104
104
  alpha/providers/ldap_provider.py,sha256=R3jCammHPS8Krd4G6W1t-nT7RAmPW-JCoirhJwdr2Bo,13542
105
- alpha/providers/oidc_provider.py,sha256=9qgKLNVFpZNUCUzXuzvBcrQY4ne2PiMQDQk2qBkfhEk,13848
105
+ alpha/providers/oidc_provider.py,sha256=t1ftGcDjZuopuvb0qyPexFdNzhsKhiatxnfLb2HBP8g,13812
106
106
  alpha/providers/models/__init__.py,sha256=SI-qTA3JMOxXx4o1h7frk8x3PRj0g_GSCEbLzyvw7CI,409
107
107
  alpha/providers/models/credentials.py,sha256=IjGm1MpnUrwnTo1Of5eacNtkh72Iqj2uKTsoyq1po_o,1304
108
- alpha/providers/models/identity.py,sha256=IWd7m3aO1TsL4JhsbVZ7Q2EAk6oGeMqwU_gclZFLKFY,14930
108
+ alpha/providers/models/identity.py,sha256=6-OezJs7Ld_4HIcnAHKREymDcOUhV5YmR7JJNVYxrrA,14978
109
109
  alpha/providers/models/token.py,sha256=CiEKTcv5CpusF7-jlvUPKtBEsCo2vrV_btt-f1fwIz0,3625
110
110
  alpha/repositories/__init__.py,sha256=qN7iefBr93_fXo8cTn_RUFaz8PPhsUlzojKbYFM2m-c,306
111
111
  alpha/repositories/rest_api_repository.py,sha256=vWfl6oTHJ6kzesCM31zag3je7R6hh8GBnO1mgOOdBNI,45797
@@ -113,7 +113,7 @@ alpha/repositories/sql_alchemy_repository.py,sha256=QMBbtSwv7HkrIgl-qQOQ9gRuiF_u
113
113
  alpha/repositories/models/__init__.py,sha256=PY3kFG0z5r5ALcwBFXVNO9GAVNn1tVTkvUp8XoZEMuY,109
114
114
  alpha/repositories/models/repository_model.py,sha256=WpfW90HpDvwDK5jcqNcprIKugqxcMaQWoqEFEgr0FoA,1038
115
115
  alpha/services/__init__.py,sha256=yeUbS-N0127DSejbCAv9jbdKuHRa_H5Ue0CtsWf4ghI,224
116
- alpha/services/authentication_service.py,sha256=2hwhnajiOkio_AJ8Ptf1c7r319dcrckGxMRiBIu48s8,38052
116
+ alpha/services/authentication_service.py,sha256=B-KuXdav66dk5ts8TXck8i9DCAvLyBhuTiM6Cdqskq0,38437
117
117
  alpha/services/user_lifecycle_management.py,sha256=yj0ITtD_WOCVo54KaNSC29XPixxdKi9ArvCMeQm6WKA,2696
118
118
  alpha/services/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
119
  alpha/services/models/cookie.py,sha256=vxNLPtMmOGlxFLKdccxEV7hd_mPKmFCEao8SczNOilo,132
@@ -122,22 +122,23 @@ alpha/utils/_http_codes.py,sha256=8b0oieqi8iCWGBvKT2ShcKxj_7JZ5rpXyzxPft2T2po,68
122
122
  alpha/utils/cookie.py,sha256=V0BTBG_AsvJEGoBv5_6n4LYWt36YJfGT2MVkTvWfxTU,1654
123
123
  alpha/utils/is_attrs.py,sha256=7EOltIJlhK9uF_nVLLCbaQV5EH8PO-ssqZcTs94y3Ic,426
124
124
  alpha/utils/is_pydantic.py,sha256=-P4n3n59_YftVqvIIQ_GovxUxQMFIt8htTDd0j2hFis,629
125
- alpha/utils/logging_configurator.py,sha256=lji_hDxY_c4RHp30etWB4H8R6YjQJt3n0a9IK1RAPUo,3785
125
+ alpha/utils/logging_configurator.py,sha256=wnwcBjI4wzvz2R0oMgEHffoGo9FfgPhf-1ZIlA9Eydo,3817
126
126
  alpha/utils/logging_level_checker.py,sha256=elCxkVW07BnVGPhIHf_TQyHCAPpdNY8xp5bszrziRS0,697
127
127
  alpha/utils/request_headers.py,sha256=z_0mDT4NoQimG1zPCyM5-7T64BJ91eQXNILuIc08cYI,4310
128
- alpha/utils/response_object.py,sha256=OTi0WoXs0zn9sDxtzn1LjL02jr65hDvchhGtUVgyAyI,4912
128
+ alpha/utils/response_object.py,sha256=AymBt3qrirpLRkS0DcVkoxVLKZ3R0Eb6A5cN4cgFs9E,5372
129
129
  alpha/utils/secret_generator.py,sha256=LjsKb8oDfsfmCCcTKrqtw_duqJ7u3V_o6zGGuG26_XE,393
130
- alpha/utils/verify_identity.py,sha256=tnh9JAez8FlrqN4Bqh1bfTdnEOM2VA1ZAVLfZCqI-ZU,2229
130
+ alpha/utils/verify_identity.py,sha256=Y1pdcVl8Z_I4ibw2KY7i5QwTQrY29WwpBOJ6j0YOAow,2271
131
131
  alpha/utils/version_checker.py,sha256=W8v69hDbrr0VvJ40gpFmXMtVawrDmUz_YGWssKUTmVE,380
132
132
  alpha/utils/openapi_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
133
133
  alpha/utils/openapi_test/container.py,sha256=70XIe_ue6TjVkAVEf8mEG3_7tkSsJKp8-sP_KYgIQG8,6404
134
134
  alpha/utils/openapi_test/exceptions.py,sha256=FfVodhZrajSGQEZngKZRmlJCxjQP502OqQgu3qdtuN4,298
135
135
  alpha/utils/openapi_test/models.py,sha256=vAHbjmwc0rVsCj9m8iKqkwPpSkSmkTOT8UQvffkrHig,2364
136
136
  alpha/utils/openapi_test/orm.py,sha256=Py95GV_0e7wI1MYDZR1_EHkvTgev6eNo70SVmnD7kGc,3045
137
- alpha/utils/openapi_test/service.py,sha256=MhlmUmJ-m-0dGacBjUQvURufESkDPOlSKkMuwObQCYA,5158
138
- alpha_python-0.6.0.dist-info/licenses/LICENSE,sha256=5KwEqC3KUoH4lVXgZ9tGriKOl-LGxHkXBWo16mFmAYM,1070
139
- alpha_python-0.6.0.dist-info/METADATA,sha256=10l6fkaMRY_HP0LY_uximYw0KQIJDn1r7PLtimEPmTQ,4678
140
- alpha_python-0.6.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
141
- alpha_python-0.6.0.dist-info/entry_points.txt,sha256=LBEXdcofOugYYdZ46nz5Dxj_aj1QbRBkumfPGhy-GXI,41
142
- alpha_python-0.6.0.dist-info/top_level.txt,sha256=tqmNnOmi2RSSiPo99C03fD5Cc3r9za9xTjPAoQC1EGA,6
143
- alpha_python-0.6.0.dist-info/RECORD,,
137
+ alpha/utils/openapi_test/response.py,sha256=IxbQ6Nw258LLViHkgfjOpc7zWGTP8ofVOX1zrwDoR50,270
138
+ alpha/utils/openapi_test/service.py,sha256=ycrEUlQmygKthnOhEiir-wtbPFi5cT-bepVclaL9FAk,5003
139
+ alpha_python-0.6.2.dist-info/licenses/LICENSE,sha256=5KwEqC3KUoH4lVXgZ9tGriKOl-LGxHkXBWo16mFmAYM,1070
140
+ alpha_python-0.6.2.dist-info/METADATA,sha256=K6meojtl5hwwRM-AA3uvAfsg3oKZL42l3lK4S53jlTE,8645
141
+ alpha_python-0.6.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
142
+ alpha_python-0.6.2.dist-info/entry_points.txt,sha256=LBEXdcofOugYYdZ46nz5Dxj_aj1QbRBkumfPGhy-GXI,41
143
+ alpha_python-0.6.2.dist-info/top_level.txt,sha256=tqmNnOmi2RSSiPo99C03fD5Cc3r9za9xTjPAoQC1EGA,6
144
+ alpha_python-0.6.2.dist-info/RECORD,,
@@ -1,117 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: alpha-python
3
- Version: 0.6.0
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
- Author-email: Bart Reijling <bart@reijling.eu>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/breijling/alpha
8
- Project-URL: Issues, https://github.com/breijling/alpha/issues
9
- Project-URL: Documentation, https://alpha-python.readthedocs.io/en/latest/
10
- Project-URL: Changelog, https://alpha-python.readthedocs.io/en/latest/changelog/
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3 :: Only
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3.14
19
- Requires-Python: <3.15,>=3.11
20
- Description-Content-Type: text/markdown
21
- License-File: LICENSE
22
- Requires-Dist: attrs>=25.4.0
23
- Requires-Dist: gunicorn>=23.0.0
24
- Requires-Dist: jsonpatch>=1.33
25
- Requires-Dist: numpy>=2.3.5
26
- Requires-Dist: pandas>=1.13
27
- Requires-Dist: pydantic>=2.12.5
28
- Requires-Dist: pyjwt>=2.10.1
29
- Requires-Dist: six>=1.17.0
30
- Requires-Dist: sqlalchemy>=2.0.44
31
- Requires-Dist: requests>=2.28.1
32
- Requires-Dist: dependency-injector[yaml]!=4.48.3,<5.0.0,>=4.42.0
33
- Requires-Dist: argon2-cffi>=25.1.0
34
- Requires-Dist: httpx
35
- Requires-Dist: cryptography
36
- Provides-Extra: api-generator
37
- Requires-Dist: openapi-generator-cli==7.14.0; extra == "api-generator"
38
- Requires-Dist: jdk4py; extra == "api-generator"
39
- Provides-Extra: flask
40
- Requires-Dist: connexion[swagger-ui]<3,>=2.14.2; extra == "flask"
41
- Requires-Dist: swagger-ui-bundle>=0.0.2; extra == "flask"
42
- Requires-Dist: python_dateutil>=2.6.0; extra == "flask"
43
- Requires-Dist: itsdangerous==1.1.0; extra == "flask"
44
- Requires-Dist: MarkupSafe<2.0.2; extra == "flask"
45
- Requires-Dist: Jinja2==2.11.2; extra == "flask"
46
- Requires-Dist: Flask<2,>=1.1.2; extra == "flask"
47
- Requires-Dist: Werkzeug<2.1.0,>=1.0.1; extra == "flask"
48
- Requires-Dist: flask-cors>=3.0.10; extra == "flask"
49
- Requires-Dist: Flask-Compress>=1.13; extra == "flask"
50
- Provides-Extra: mysql
51
- Requires-Dist: pymysql>=1.1.2; extra == "mysql"
52
- Provides-Extra: postgresql
53
- Requires-Dist: psycopg2-binary>=2.9.11; extra == "postgresql"
54
- Provides-Extra: ldap
55
- Requires-Dist: ldap3>=2.9.1; extra == "ldap"
56
- Dynamic: license-file
57
-
58
- # Alpha
59
-
60
- 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.
61
-
62
- ## Badges
63
-
64
- [![PyPI version](https://badge.fury.io/py/alpha-python.svg)](https://badge.fury.io/py/alpha-python)
65
- [![Build Status](https://travis-ci.com/breijling/alpha.svg?branch=main)](https://travis-ci.com/breijling/alpha)
66
- [![Coverage Status](https://coveralls.io/repos/github/breijling/alpha/badge.svg?branch=main)](https://coveralls.io/github/breijling/alpha?branch=main)
67
- [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
68
-
69
- ## Documentation
70
-
71
- [![Documentation Status](https://readthedocs.org/projects/alpha-python/badge/?version=latest)](https://alpha-python.readthedocs.io/en/latest/)
72
-
73
- Full documentation is available at [alpha-python.readthedocs.io](https://alpha-python.readthedocs.io/).
74
-
75
- ## Installation
76
-
77
- The library is still in development, but you can already install it using pip:
78
-
79
- ```shell
80
- pip install alpha-python
81
- ```
82
-
83
- If you want to use the alpha cli for generating API code, you can install it using pip as well:
84
-
85
- ```shell
86
- pip install alpha-python[api-generator]
87
- ```
88
-
89
- If you want to add the library to your API project, you can add it to your pyproject.toml file:
90
-
91
- ```shell
92
- # Poetry example
93
- poetry add alpha-python --extras "flask, postgresql"
94
- poetry add --dev alpha-python --extras "api-generator"
95
-
96
- # UV example
97
- uv add alpha-python --extra flask --extra postgresql
98
- uv add --dev alpha-python --extra api-generator
99
- ```
100
-
101
- ## Usage
102
-
103
- The library contains a lot of different components, but the most important ones are:
104
-
105
- - `alpha.encoder.JSONEncoder`: A JSON encoder that can be used to serialize complex objects to JSON.
106
-
107
- ## Features
108
-
109
-
110
-
111
- ## Contributing
112
-
113
- If you want to contribute to the development of this library, you can fork the repository and create a pull request with your changes.
114
-
115
- ## License
116
-
117
- This library is licensed under the MIT License. See the LICENSE file for more information.