alpha-python 0.6.0__py3-none-any.whl → 0.6.1__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/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
 
@@ -214,6 +214,8 @@ def {{operationId}}(
214
214
  {{/allParams}}
215
215
  {{/vendorExtensions.x-alpha-factory}}
216
216
 
217
+ 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}}
218
+
217
219
  try:
218
220
  # Objects used for authorization
219
221
  roles=[{{#vendorExtensions.x-alpha-verify-roles}}"{{.}}",{{/vendorExtensions.x-alpha-verify-roles}}]
@@ -254,7 +256,7 @@ def {{operationId}}(
254
256
  {{/vendorExtensions.x-alpha-service-name}}
255
257
  {{^vendorExtensions.x-alpha-service-name}}
256
258
  {{#vendorExtensions.x-alpha-custom-function}}
257
- result = {{vendorExtensions.x-alpha-custom-function}}
259
+ result = {{{vendorExtensions.x-alpha-custom-function}}}
258
260
  {{/vendorExtensions.x-alpha-custom-function}}
259
261
  {{/vendorExtensions.x-alpha-service-name}}
260
262
  {{/vendorExtensions.x-alpha-request-factory}}
@@ -270,7 +272,7 @@ def {{operationId}}(
270
272
  {{/vendorExtensions.x-alpha-custom-function}}
271
273
  {{/vendorExtensions.x-alpha-service-method}}
272
274
  {{#vendorExtensions.x-alpha-custom-function}}
273
- result = {{vendorExtensions.x-alpha-custom-function}}
275
+ result = {{{vendorExtensions.x-alpha-custom-function}}}
274
276
  {{/vendorExtensions.x-alpha-custom-function}}
275
277
  {{/vendorExtensions.x-alpha-service-name}}
276
278
  {{#vendorExtensions.x-alpha-service-name}}
@@ -302,7 +304,7 @@ def {{operationId}}(
302
304
  cls={{vendorExtensions.x-alpha-response-factory}}
303
305
  )
304
306
  {{/vendorExtensions.x-alpha-response-factory}}
305
- response_object, status_code = create_response_object(
307
+ return response_object_function(
306
308
  http_codes=http_codes,
307
309
  status_code={{code}},
308
310
  status_message='{{message}}',
@@ -310,10 +312,9 @@ def {{operationId}}(
310
312
  {{#vendorExtensions.x-content-type}}data_type='{{vendorExtensions.x-content-type}}'{{/vendorExtensions.x-content-type}}
311
313
  {{#vendorExtensions.x-alpha-cookie-support}}response_type='flask'{{/vendorExtensions.x-alpha-cookie-support}}
312
314
  )
313
- return response_object, status_code
314
315
  {{/returnType}}
315
316
  {{^returnType}}
316
- return create_response_object(
317
+ return response_object_function(
317
318
  http_codes=http_codes,
318
319
  status_code=204,
319
320
  status_message='{{message}}',
@@ -326,7 +327,7 @@ def {{operationId}}(
326
327
  {{#is4xx}}
327
328
  {{#vendorExtensions.x-alpha-exception}}
328
329
  except {{vendorExtensions.x-alpha-exception}} as exc:
329
- return create_response_object(
330
+ return response_object_function(
330
331
  http_codes=http_codes,
331
332
  status_code={{code}},
332
333
  status_message=f'{exc}'
@@ -336,7 +337,7 @@ def {{operationId}}(
336
337
  {{#is5xx}}
337
338
  {{#vendorExtensions.x-alpha-exception}}
338
339
  except {{vendorExtensions.x-alpha-exception}} as exc:
339
- return create_response_object(
340
+ return response_object_function(
340
341
  http_codes=http_codes,
341
342
  status_code={{code}},
342
343
  status_message=f'{exc}'
@@ -345,92 +346,92 @@ def {{operationId}}(
345
346
  {{/is5xx}}
346
347
  {{/responses}}
347
348
  except BadRequestException as exc:
348
- return create_response_object(
349
+ return response_object_function(
349
350
  http_codes=http_codes,
350
351
  status_code=400,
351
352
  status_message=f'{exc}'
352
353
  )
353
354
  except UnauthorizedException as exc:
354
- return create_response_object(
355
+ return response_object_function(
355
356
  http_codes=http_codes,
356
357
  status_code=401,
357
358
  status_message=f'{exc}'
358
359
  )
359
360
  except ForbiddenException as exc:
360
- return create_response_object(
361
+ return response_object_function(
361
362
  http_codes=http_codes,
362
363
  status_code=403,
363
364
  status_message=f'{exc}'
364
365
  )
365
366
  except NotFoundException as exc:
366
- return create_response_object(
367
+ return response_object_function(
367
368
  http_codes=http_codes,
368
369
  status_code=404,
369
370
  status_message=f'{exc}'
370
371
  )
371
372
  except MethodNotAllowedException as exc:
372
- return create_response_object(
373
+ return response_object_function(
373
374
  http_codes=http_codes,
374
375
  status_code=405,
375
376
  status_message=f'{exc}'
376
377
  )
377
378
  except NotAcceptableException as exc:
378
- return create_response_object(
379
+ return response_object_function(
379
380
  http_codes=http_codes,
380
381
  status_code=406,
381
382
  status_message=f'{exc}'
382
383
  )
383
384
  except ConflictException as exc:
384
- return create_response_object(
385
+ return response_object_function(
385
386
  http_codes=http_codes,
386
387
  status_code=409,
387
388
  status_message=f'{exc}'
388
389
  )
389
390
  except PayloadTooLargeException as exc:
390
- return create_response_object(
391
+ return response_object_function(
391
392
  http_codes=http_codes,
392
393
  status_code=413,
393
394
  status_message=f'{exc}'
394
395
  )
395
396
  except UnprocessableContentException as exc:
396
- return create_response_object(
397
+ return response_object_function(
397
398
  http_codes=http_codes,
398
399
  status_code=422,
399
400
  status_message=f'{exc}'
400
401
  )
401
402
  except NotImplementedException as exc:
402
- return create_response_object(
403
+ return response_object_function(
403
404
  http_codes=http_codes,
404
405
  status_code=501,
405
406
  status_message=f'{exc}'
406
407
  )
407
408
  except BadGatewayException as exc:
408
- return create_response_object(
409
+ return response_object_function(
409
410
  http_codes=http_codes,
410
411
  status_code=502,
411
412
  status_message=f'{exc}'
412
413
  )
413
414
  except ServiceUnavailableException as exc:
414
- return create_response_object(
415
+ return response_object_function(
415
416
  http_codes=http_codes,
416
417
  status_code=503,
417
418
  status_message=f'{exc}'
418
419
  )
419
420
  except GatewayTimeoutException as exc:
420
- return create_response_object(
421
+ return response_object_function(
421
422
  http_codes=http_codes,
422
423
  status_code=504,
423
424
  status_message=f'{exc}'
424
425
  )
425
426
  except ServerErrorException as exc:
426
- return create_response_object(
427
+ return response_object_function(
427
428
  http_codes=http_codes,
428
429
  status_code=500,
429
430
  status_message=f'{exc}'
430
431
  )
431
432
  except Exception as exc:
432
433
  traceback.print_exc()
433
- return create_response_object(
434
+ return response_object_function(
434
435
  http_codes=http_codes,
435
436
  status_code=500,
436
437
  status_message=f'{exc}'
@@ -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(
@@ -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.1
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
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
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=ittIpaeSeXfpmSEYm2HhLejjh4YcAmvOmSzWzdtnRUQ,17528
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
@@ -74,9 +74,9 @@ alpha/infra/databases/__init__.py,sha256=IyA_aB9h_7rOM-aa50QLsA_q4e9W4HnJOqh76aD
74
74
  alpha/infra/databases/sql_alchemy.py,sha256=7t17M8UEgW1NMX9LYW6A14EM5UxEJZyp-8a_xLEONAs,235
75
75
  alpha/infra/models/__init__.py,sha256=SIakw9bAPXwFmx3I7exCRBs3e2Ug3etOJy6JyIOR1Uc,348
76
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
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
@@ -91,7 +91,7 @@ alpha/interfaces/pydantic_instance.py,sha256=S96jLEWRmDyvYV3VqofSqjiN-lcjfkjiNp7
91
91
  alpha/interfaces/sql_database.py,sha256=H52NR1ti2j9G9zJsibPtKmXL7WdHOzeSyPXO4Zq9wlA,996
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.1.dist-info/licenses/LICENSE,sha256=5KwEqC3KUoH4lVXgZ9tGriKOl-LGxHkXBWo16mFmAYM,1070
140
+ alpha_python-0.6.1.dist-info/METADATA,sha256=TBRRf51v3Gldx6fFVhi8Tqva-txOBAJpUbizSmiiBCk,8645
141
+ alpha_python-0.6.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
142
+ alpha_python-0.6.1.dist-info/entry_points.txt,sha256=LBEXdcofOugYYdZ46nz5Dxj_aj1QbRBkumfPGhy-GXI,41
143
+ alpha_python-0.6.1.dist-info/top_level.txt,sha256=tqmNnOmi2RSSiPo99C03fD5Cc3r9za9xTjPAoQC1EGA,6
144
+ alpha_python-0.6.1.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.