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 +27 -23
- alpha/encoder.py +4 -3
- alpha/factories/jwt_factory.py +10 -7
- alpha/factories/password_factory.py +8 -1
- alpha/handlers/api_generate_handler.py +5 -0
- alpha/handlers/templates/python-flask/__main__.mustache +26 -9
- alpha/handlers/templates/python-flask/controller.mustache +23 -22
- alpha/infra/models/json_patch.py +1 -1
- alpha/infra/models/order_by.py +4 -2
- alpha/infra/models/query_clause.py +2 -2
- alpha/interfaces/token_factory.py +5 -3
- alpha/providers/models/identity.py +5 -5
- alpha/providers/oidc_provider.py +0 -2
- alpha/services/authentication_service.py +10 -4
- alpha/utils/logging_configurator.py +1 -1
- alpha/utils/openapi_test/response.py +12 -0
- alpha/utils/openapi_test/service.py +0 -6
- alpha/utils/response_object.py +10 -1
- alpha/utils/verify_identity.py +7 -7
- alpha_python-0.6.1.dist-info/METADATA +210 -0
- {alpha_python-0.6.0.dist-info → alpha_python-0.6.1.dist-info}/RECORD +25 -24
- alpha_python-0.6.0.dist-info/METADATA +0 -117
- {alpha_python-0.6.0.dist-info → alpha_python-0.6.1.dist-info}/WHEEL +0 -0
- {alpha_python-0.6.0.dist-info → alpha_python-0.6.1.dist-info}/entry_points.txt +0 -0
- {alpha_python-0.6.0.dist-info → alpha_python-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {alpha_python-0.6.0.dist-info → alpha_python-0.6.1.dist-info}/top_level.txt +0 -0
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=
|
|
23
|
+
description="Alpha command line interface.",
|
|
24
24
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
25
25
|
)
|
|
26
26
|
subparsers = parser.add_subparsers(
|
|
27
|
-
title=
|
|
28
|
-
dest=
|
|
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=
|
|
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[
|
|
92
|
-
del handler_args[
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
135
|
+
with open(pyproject_path, "rb") as f:
|
|
136
136
|
pyproject_data = tomllib.load(f)
|
|
137
137
|
name = None
|
|
138
138
|
try:
|
|
139
|
-
if
|
|
140
|
-
name = pyproject_data[
|
|
141
|
-
elif
|
|
142
|
-
|
|
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(
|
|
149
|
+
print("Could not find project name in pyproject.toml")
|
|
147
150
|
except Exception:
|
|
148
151
|
pass
|
|
149
152
|
else:
|
|
150
|
-
print(
|
|
153
|
+
print("Could not find pyproject.toml")
|
|
151
154
|
|
|
152
155
|
# Fallback to use the current folder name
|
|
153
|
-
print(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
alpha/factories/jwt_factory.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
434
|
+
return response_object_function(
|
|
434
435
|
http_codes=http_codes,
|
|
435
436
|
status_code=500,
|
|
436
437
|
status_message=f'{exc}'
|
alpha/infra/models/json_patch.py
CHANGED
alpha/infra/models/order_by.py
CHANGED
|
@@ -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) ->
|
|
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(
|
|
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[
|
|
461
|
-
) -> Sequence[
|
|
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
|
|
alpha/providers/oidc_provider.py
CHANGED
|
@@ -379,12 +379,12 @@ class AuthenticationService:
|
|
|
379
379
|
"Logout successful",
|
|
380
380
|
)
|
|
381
381
|
|
|
382
|
-
def verify(self,
|
|
383
|
-
"""Verify
|
|
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
|
-
|
|
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=
|
|
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
|
|
@@ -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
|
alpha/utils/response_object.py
CHANGED
|
@@ -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
|
-
|
|
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"
|
alpha/utils/verify_identity.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
+
[](https://badge.fury.io/py/alpha-python)
|
|
65
|
+
[](https://pypistats.org/packages/alpha-python)
|
|
66
|
+
[](https://github.com/breijling/alpha/actions/workflows/python-app.yml)
|
|
67
|
+
[](https://pypi.org/project/alpha-python)
|
|
68
|
+
[](https://coveralls.io/github/BReijling/alpha?branch=main)
|
|
69
|
+
[](https://docs.astral.sh/uv/)
|
|
70
|
+
[](https://mypy-lang.org/)
|
|
71
|
+
[](https://docs.pytest.org/)
|
|
72
|
+
[](https://www.python.org/dev/peps/pep-0008/)
|
|
73
|
+
[](https://github.com/astral-sh/ruff)
|
|
74
|
+
[](https://alpha-python.readthedocs.io/en/latest/)
|
|
75
|
+
[](https://choosealicense.com/licenses/mit/)
|
|
76
|
+
[](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=
|
|
3
|
-
alpha/encoder.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
78
|
-
alpha/infra/models/order_by.py,sha256=
|
|
79
|
-
alpha/infra/models/query_clause.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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/
|
|
138
|
-
|
|
139
|
-
alpha_python-0.6.
|
|
140
|
-
alpha_python-0.6.
|
|
141
|
-
alpha_python-0.6.
|
|
142
|
-
alpha_python-0.6.
|
|
143
|
-
alpha_python-0.6.
|
|
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
|
-
[](https://badge.fury.io/py/alpha-python)
|
|
65
|
-
[](https://travis-ci.com/breijling/alpha)
|
|
66
|
-
[](https://coveralls.io/github/breijling/alpha?branch=main)
|
|
67
|
-
[](https://choosealicense.com/licenses/mit/)
|
|
68
|
-
|
|
69
|
-
## Documentation
|
|
70
|
-
|
|
71
|
-
[](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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|