tiferet-fast 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tiferet_fast/__init__.py +15 -0
- tiferet_fast/contexts/__init__.py +7 -0
- tiferet_fast/contexts/fast.py +207 -0
- tiferet_fast/contexts/request.py +62 -0
- tiferet_fast/contracts/__init__.py +10 -0
- tiferet_fast/contracts/fast.py +84 -0
- tiferet_fast/data/__init__.py +10 -0
- tiferet_fast/data/fast.py +146 -0
- tiferet_fast/handlers/__init__.py +8 -0
- tiferet_fast/handlers/fast.py +99 -0
- tiferet_fast/models/__init__.py +9 -0
- tiferet_fast/models/fast.py +119 -0
- tiferet_fast/proxies/__init__.py +3 -0
- tiferet_fast/proxies/yaml/__init__.py +8 -0
- tiferet_fast/proxies/yaml/fast.py +143 -0
- tiferet_fast-0.1.0.dist-info/METADATA +283 -0
- tiferet_fast-0.1.0.dist-info/RECORD +20 -0
- tiferet_fast-0.1.0.dist-info/WHEEL +5 -0
- tiferet_fast-0.1.0.dist-info/licenses/LICENSE +21 -0
- tiferet_fast-0.1.0.dist-info/top_level.txt +1 -0
tiferet_fast/__init__.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
"""Tiferet Fast - A FastAPI Framework"""
|
2
|
+
|
3
|
+
# *** exports
|
4
|
+
|
5
|
+
# ** app
|
6
|
+
# Export the main application context and related modules.
|
7
|
+
# Use a try-except block to avoid import errors on build systems.
|
8
|
+
try:
|
9
|
+
from .contexts import FastApiContext
|
10
|
+
except:
|
11
|
+
pass
|
12
|
+
|
13
|
+
# *** version
|
14
|
+
|
15
|
+
__version__ = "0.1.0"
|
@@ -0,0 +1,207 @@
|
|
1
|
+
"""Fast API Context for Tiferet framework."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import Any, Callable
|
7
|
+
from functools import partial
|
8
|
+
|
9
|
+
# ** infra
|
10
|
+
from tiferet import TiferetError
|
11
|
+
from tiferet.contexts import (
|
12
|
+
AppInterfaceContext,
|
13
|
+
FeatureContext,
|
14
|
+
ErrorContext,
|
15
|
+
LoggingContext
|
16
|
+
)
|
17
|
+
from fastapi import FastAPI
|
18
|
+
from fastapi.routing import APIRouter
|
19
|
+
from starlette.middleware import Middleware
|
20
|
+
from starlette_context import context, plugins
|
21
|
+
from starlette_context.middleware import RawContextMiddleware
|
22
|
+
|
23
|
+
# ** app
|
24
|
+
from .request import FastRequestContext
|
25
|
+
from ..handlers import FastApiHandler
|
26
|
+
from ..models import FastRouter
|
27
|
+
|
28
|
+
# *** contexts
|
29
|
+
|
30
|
+
# ** context: fast_api_context
|
31
|
+
class FastApiContext(AppInterfaceContext):
|
32
|
+
'''
|
33
|
+
A context for managing Fast API interactions within the Tiferet framework.
|
34
|
+
'''
|
35
|
+
|
36
|
+
# * attribute: fast_app
|
37
|
+
fast_app: FastAPI
|
38
|
+
|
39
|
+
# * attribute: fast_api_handler
|
40
|
+
fast_api_handler: FastApiHandler
|
41
|
+
|
42
|
+
# * init
|
43
|
+
def __init__(self,
|
44
|
+
interface_id: str,
|
45
|
+
features: FeatureContext,
|
46
|
+
errors: ErrorContext,
|
47
|
+
logging: LoggingContext,
|
48
|
+
fast_api_handler: FastApiHandler
|
49
|
+
):
|
50
|
+
'''
|
51
|
+
Initialize the Fast API context.
|
52
|
+
|
53
|
+
:param interface_id: The interface ID.
|
54
|
+
:type interface_id: str
|
55
|
+
:param features: The feature context.
|
56
|
+
:type features: FeatureContext
|
57
|
+
:param errors: The error context.
|
58
|
+
:type errors: ErrorContext
|
59
|
+
:param logging: The logging context.
|
60
|
+
:type logging: LoggingContext
|
61
|
+
:param fast_api_handler: The Fast API handler.
|
62
|
+
:type fast_api_handler: FastApiHandler
|
63
|
+
'''
|
64
|
+
|
65
|
+
# Call the parent constructor.
|
66
|
+
super().__init__(interface_id, features, errors, logging)
|
67
|
+
|
68
|
+
# Set the attributes.
|
69
|
+
self.fast_api_handler = fast_api_handler
|
70
|
+
|
71
|
+
# * method: parse_request
|
72
|
+
def parse_request(self, headers: dict = {}, data: dict = {}, feature_id: str = None, **kwargs) -> FastRequestContext:
|
73
|
+
'''
|
74
|
+
Parse the incoming request and return a FastRequestContext instance.
|
75
|
+
|
76
|
+
:param headers: The request headers.
|
77
|
+
:type headers: dict
|
78
|
+
:param data: The request data.
|
79
|
+
:type data: dict
|
80
|
+
:param feature_id: The feature ID.
|
81
|
+
:type feature_id: str
|
82
|
+
:param kwargs: Additional keyword arguments.
|
83
|
+
:type kwargs: dict
|
84
|
+
:return: A FastRequestContext instance.
|
85
|
+
:rtype: FastRequestContext
|
86
|
+
'''
|
87
|
+
|
88
|
+
# Return a FastRequestContext instance.
|
89
|
+
return FastRequestContext(
|
90
|
+
headers=headers,
|
91
|
+
data=data,
|
92
|
+
feature_id=feature_id
|
93
|
+
)
|
94
|
+
|
95
|
+
# * method: handle_error
|
96
|
+
def handle_error(self, error: Exception) -> Any:
|
97
|
+
'''
|
98
|
+
Handle the error and return the response.
|
99
|
+
|
100
|
+
:param error: The error to handle.
|
101
|
+
:type error: Exception
|
102
|
+
:return: The error response.
|
103
|
+
:rtype: Any
|
104
|
+
'''
|
105
|
+
|
106
|
+
# Handle the error and get the response from the parent context.
|
107
|
+
if not isinstance(error, TiferetError):
|
108
|
+
return super().handle_error(error), 500
|
109
|
+
|
110
|
+
# Get the status code by the error code on the exception.
|
111
|
+
status_code = self.fast_api_handler.get_status_code(error.error_code)
|
112
|
+
return super().handle_error(error), status_code
|
113
|
+
|
114
|
+
# * method: handle_response
|
115
|
+
def handle_response(self, request: FastRequestContext) -> Any:
|
116
|
+
'''
|
117
|
+
Handle the response from the request context.
|
118
|
+
|
119
|
+
:param request: The request context.
|
120
|
+
:type request: RequestContext
|
121
|
+
:return: The response.
|
122
|
+
:rtype: Any
|
123
|
+
'''
|
124
|
+
|
125
|
+
# Handle the response from the request context.
|
126
|
+
response = super().handle_response(request)
|
127
|
+
|
128
|
+
# Retrieve the route by the request feature id.
|
129
|
+
route = self.fast_api_handler.get_route(request.feature_id)
|
130
|
+
|
131
|
+
# Return the result with the specified status code.
|
132
|
+
return response, route.status_code if route else 200
|
133
|
+
|
134
|
+
# * method: build_router
|
135
|
+
def build_router(self, fast_router: FastRouter, view_func: Callable, **kwargs) -> FastAPI:
|
136
|
+
'''
|
137
|
+
Assembles a FastAPI router from the given FastRouter model.
|
138
|
+
|
139
|
+
:param fast_router: The FastRouter model.
|
140
|
+
:type fast_router: FastRouter
|
141
|
+
:param view_func: The view function to handle requests.
|
142
|
+
:type view_func: Callable
|
143
|
+
:param kwargs: Additional keyword arguments.
|
144
|
+
:type kwargs: dict
|
145
|
+
:return: The created FastAPI router.
|
146
|
+
:rtype: FastAPI
|
147
|
+
'''
|
148
|
+
|
149
|
+
# Create a FastAPI router instance.
|
150
|
+
router = APIRouter(
|
151
|
+
prefix=fast_router.prefix,
|
152
|
+
tags=[fast_router.name]
|
153
|
+
)
|
154
|
+
|
155
|
+
# Add the routes to the router.
|
156
|
+
for route in fast_router.routes:
|
157
|
+
router.add_api_route(
|
158
|
+
name=route.endpoint,
|
159
|
+
path=route.path,
|
160
|
+
endpoint=partial(view_func),
|
161
|
+
methods=route.methods,
|
162
|
+
status_code=route.status_code
|
163
|
+
)
|
164
|
+
|
165
|
+
# Return the created router.
|
166
|
+
return router
|
167
|
+
|
168
|
+
# * method: build_fast_app
|
169
|
+
def build_fast_app(self, view_func: Callable, **kwargs) -> FastAPI:
|
170
|
+
'''
|
171
|
+
Build and return a FastAPI application instance.
|
172
|
+
|
173
|
+
:param view_func: The view function to handle requests.
|
174
|
+
:type view_func: Callable
|
175
|
+
:param kwargs: Additional keyword arguments.
|
176
|
+
:type kwargs: dict
|
177
|
+
:return: A FastAPI application instance.
|
178
|
+
:rtype: FastAPI
|
179
|
+
'''
|
180
|
+
|
181
|
+
# Create middleware for context management.
|
182
|
+
middleware = [
|
183
|
+
Middleware(
|
184
|
+
RawContextMiddleware,
|
185
|
+
plugins=(
|
186
|
+
plugins.RequestIdPlugin(),
|
187
|
+
plugins.CorrelationIdPlugin(),
|
188
|
+
)
|
189
|
+
)
|
190
|
+
]
|
191
|
+
|
192
|
+
# Create the FastAPI application.
|
193
|
+
fast_app = FastAPI(title=f"{self.interface_id} API", middleware=middleware)
|
194
|
+
|
195
|
+
# Load the FastAPI routers.
|
196
|
+
routers = self.fast_api_handler.get_routers()
|
197
|
+
|
198
|
+
# Create and include the routers.
|
199
|
+
for router in routers:
|
200
|
+
fast_router = self.build_router(router, view_func=view_func, **kwargs)
|
201
|
+
fast_app.include_router(fast_router)
|
202
|
+
|
203
|
+
# Set the fast_app attribute.
|
204
|
+
self.fast_app = fast_app
|
205
|
+
|
206
|
+
# Return the FastAPI application.
|
207
|
+
return fast_app
|
@@ -0,0 +1,62 @@
|
|
1
|
+
"""Fast API Request Context"""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
# ** infra
|
9
|
+
from tiferet.contexts.request import RequestContext
|
10
|
+
from tiferet.models import ModelObject
|
11
|
+
|
12
|
+
# *** contexts
|
13
|
+
|
14
|
+
# ** context: fast_request_context
|
15
|
+
class FastRequestContext(RequestContext):
|
16
|
+
'''
|
17
|
+
A context for handling Fast API request data and responses.
|
18
|
+
'''
|
19
|
+
|
20
|
+
# * method: handle_response
|
21
|
+
def handle_response(self) -> Any:
|
22
|
+
'''
|
23
|
+
Handle the response for the Fast API request context.
|
24
|
+
|
25
|
+
:return: The response.
|
26
|
+
:rtype: Any
|
27
|
+
'''
|
28
|
+
|
29
|
+
# Set the result using the set_result method to ensure proper formatting.
|
30
|
+
self.set_result(self.result)
|
31
|
+
|
32
|
+
# Handle the response using the parent method.
|
33
|
+
return super().handle_response()
|
34
|
+
|
35
|
+
# * method: set_result
|
36
|
+
def set_result(self, result: Any):
|
37
|
+
'''
|
38
|
+
Set the result of the request context.
|
39
|
+
|
40
|
+
:param result: The result to set.
|
41
|
+
:type result: Any
|
42
|
+
'''
|
43
|
+
|
44
|
+
# If the response is None, return an empty response.
|
45
|
+
if result is None:
|
46
|
+
self.result = ''
|
47
|
+
|
48
|
+
# Convert the response to a dictionary if it's a ModelObject.
|
49
|
+
elif isinstance(result, ModelObject):
|
50
|
+
self.result = result.to_primitive()
|
51
|
+
|
52
|
+
# If the response is a list containing model objects, convert each to a dictionary.
|
53
|
+
elif isinstance(result, list) and all(isinstance(item, ModelObject) for item in result):
|
54
|
+
self.result = [item.to_primitive() for item in result]
|
55
|
+
|
56
|
+
# If the response is a dict containing model objects, convert each to a dictionary.
|
57
|
+
elif isinstance(result, dict) and all(isinstance(value, ModelObject) for value in result.values()):
|
58
|
+
self.result = {key: value.to_primitive() for key, value in result.items()}
|
59
|
+
|
60
|
+
# Otherwise, set the result directly.
|
61
|
+
else:
|
62
|
+
self.result = result
|
@@ -0,0 +1,84 @@
|
|
1
|
+
"""Fast API Contracts."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
# ** infra
|
9
|
+
from tiferet import (
|
10
|
+
ModelContract,
|
11
|
+
Repository,
|
12
|
+
abstractmethod
|
13
|
+
)
|
14
|
+
|
15
|
+
# *** contracts
|
16
|
+
|
17
|
+
# ** contract: fast_route_contract
|
18
|
+
class FastRouteContract(ModelContract):
|
19
|
+
'''
|
20
|
+
A contract for Fast route models.
|
21
|
+
'''
|
22
|
+
|
23
|
+
# * attribute: endpoint
|
24
|
+
endpoint: str
|
25
|
+
|
26
|
+
# * attribute: status_code
|
27
|
+
status_code: int
|
28
|
+
|
29
|
+
# ** contract: fast_blueprint_contract
|
30
|
+
class FastRouterContract(ModelContract):
|
31
|
+
'''
|
32
|
+
A contract for Fast blueprint models.
|
33
|
+
'''
|
34
|
+
|
35
|
+
# * attribute: name
|
36
|
+
name: str
|
37
|
+
|
38
|
+
# * attribute: routes
|
39
|
+
routes: List[FastRouteContract]
|
40
|
+
|
41
|
+
# ** contract: fast_api_repository
|
42
|
+
class FastApiRepository(Repository):
|
43
|
+
'''
|
44
|
+
A repository contract for managing Fast API entities.
|
45
|
+
'''
|
46
|
+
|
47
|
+
# * method: get_routers
|
48
|
+
@abstractmethod
|
49
|
+
def get_routers(self) -> List[FastRouterContract]:
|
50
|
+
'''
|
51
|
+
Retrieve all Fast routers.
|
52
|
+
|
53
|
+
:return: A list of FastRouterContract instances.
|
54
|
+
:rtype: List[FastRouterContract]
|
55
|
+
'''
|
56
|
+
raise NotImplementedError('get_routers method not implemented.')
|
57
|
+
|
58
|
+
# * method: get_route
|
59
|
+
@abstractmethod
|
60
|
+
def get_route(self, route_id: str, router_name: str = None) -> FastRouteContract:
|
61
|
+
'''
|
62
|
+
Retrieve a specific Fast route by its router and route IDs.
|
63
|
+
|
64
|
+
:param route_id: The ID of the route within the router.
|
65
|
+
:type route_id: str
|
66
|
+
:param router_name: The name of the router (optional).
|
67
|
+
:type router_name: str
|
68
|
+
:return: The corresponding FastRouteContract instance.
|
69
|
+
:rtype: FastRouteContract
|
70
|
+
'''
|
71
|
+
raise NotImplementedError('get_route method not implemented.')
|
72
|
+
|
73
|
+
# * method: get_status_code
|
74
|
+
@abstractmethod
|
75
|
+
def get_status_code(self, error_code: str) -> int:
|
76
|
+
'''
|
77
|
+
Retrieve the HTTP status code for a given error code.
|
78
|
+
|
79
|
+
:param error_code: The error code.
|
80
|
+
:type error_code: str
|
81
|
+
:return: The corresponding HTTP status code.
|
82
|
+
:rtype: int
|
83
|
+
'''
|
84
|
+
raise NotImplementedError('get_status_code method not implemented.')
|
@@ -0,0 +1,146 @@
|
|
1
|
+
"""Fast API Data Transfer Objects (DTOs)."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import Dict, Any
|
7
|
+
|
8
|
+
# ** infra
|
9
|
+
from tiferet.data import (
|
10
|
+
DataObject,
|
11
|
+
StringType,
|
12
|
+
DictType,
|
13
|
+
ModelType
|
14
|
+
)
|
15
|
+
|
16
|
+
# ** app
|
17
|
+
from ..models import (
|
18
|
+
FastRoute,
|
19
|
+
FastRouter
|
20
|
+
)
|
21
|
+
from ..contracts import (
|
22
|
+
FastRouteContract,
|
23
|
+
FastRouterContract
|
24
|
+
)
|
25
|
+
|
26
|
+
# *** data
|
27
|
+
|
28
|
+
# ** data: fast_route_yaml_data
|
29
|
+
class FastRouteYamlData(DataObject, FastRoute):
|
30
|
+
'''
|
31
|
+
A data object for Fast API route model from YAML.
|
32
|
+
'''
|
33
|
+
|
34
|
+
class Options:
|
35
|
+
'''
|
36
|
+
Options for the data object.
|
37
|
+
'''
|
38
|
+
serialize_when_none = False
|
39
|
+
roles = {
|
40
|
+
'to_model': DataObject.allow(),
|
41
|
+
'to_data': DataObject.deny('endpoint')
|
42
|
+
}
|
43
|
+
|
44
|
+
# * attribute: endpoint
|
45
|
+
endpoint = StringType(
|
46
|
+
metadata=dict(
|
47
|
+
description='The unique identifier of the route endpoint.'
|
48
|
+
)
|
49
|
+
)
|
50
|
+
|
51
|
+
# * method: map
|
52
|
+
def map(self, id: str, endpoint: str) -> FastRouteContract:
|
53
|
+
'''
|
54
|
+
Map the data object to a FastRouteContract instance.
|
55
|
+
|
56
|
+
:param id: The unique identifier of the route.
|
57
|
+
:type id: str
|
58
|
+
:param endpoint: The name of the route endpoint.
|
59
|
+
:type endpoint: str
|
60
|
+
:return: A FastRouteContract instance.
|
61
|
+
:rtype: FastRouteContract
|
62
|
+
'''
|
63
|
+
|
64
|
+
# Map the data object to a model instance.
|
65
|
+
return super().map(
|
66
|
+
FastRoute,
|
67
|
+
id=id,
|
68
|
+
endpoint=endpoint
|
69
|
+
)
|
70
|
+
|
71
|
+
# ** data: fast_router_yaml_data
|
72
|
+
class FastRouterYamlData(DataObject, FastRouter):
|
73
|
+
'''
|
74
|
+
A data object for Fast API router model from YAML.
|
75
|
+
'''
|
76
|
+
|
77
|
+
class Options:
|
78
|
+
'''
|
79
|
+
Options for the data object.
|
80
|
+
'''
|
81
|
+
serialize_when_none = False
|
82
|
+
roles = {
|
83
|
+
'to_model': DataObject.deny('routes'),
|
84
|
+
'to_data': DataObject.deny('name')
|
85
|
+
}
|
86
|
+
|
87
|
+
# * attribute: name
|
88
|
+
name = StringType(
|
89
|
+
metadata=dict(
|
90
|
+
description='The name of the router.'
|
91
|
+
)
|
92
|
+
)
|
93
|
+
|
94
|
+
# * attribute: routes
|
95
|
+
routes = DictType(
|
96
|
+
ModelType(FastRouteYamlData),
|
97
|
+
default={},
|
98
|
+
metadata=dict(
|
99
|
+
description='A dictionary of route endpoint to FastRouteYamlData instances.'
|
100
|
+
)
|
101
|
+
)
|
102
|
+
|
103
|
+
# * method: from_data
|
104
|
+
@staticmethod
|
105
|
+
def from_data(routes: Dict[str, Any] = {}, **kwargs) -> 'FastRouterYamlData':
|
106
|
+
'''
|
107
|
+
Create a FastRouterYamlData instance from raw data.
|
108
|
+
|
109
|
+
:param routes: A dictionary of route endpoint to route data.
|
110
|
+
:type routes: Dict[str, Any]
|
111
|
+
:return: A FastRouterYamlData instance.
|
112
|
+
:rtype: FastRouterYamlData
|
113
|
+
'''
|
114
|
+
|
115
|
+
# Convert each route in the routes dictionary to a FastRouteYamlData instance.
|
116
|
+
route_objs = {endpoint: DataObject.from_data(
|
117
|
+
FastRouteYamlData,
|
118
|
+
endpoint=endpoint,
|
119
|
+
**data
|
120
|
+
) for endpoint, data in routes.items()}
|
121
|
+
|
122
|
+
# Create and return a FastRouterYamlData instance.
|
123
|
+
return DataObject.from_data(
|
124
|
+
FastRouterYamlData,
|
125
|
+
routes=route_objs,
|
126
|
+
**kwargs
|
127
|
+
)
|
128
|
+
|
129
|
+
# * method: map
|
130
|
+
def map(self) -> FastRouterContract:
|
131
|
+
'''
|
132
|
+
Map the data object to a FastRouterContract instance.
|
133
|
+
|
134
|
+
:return: A FastRouterContract instance.
|
135
|
+
:rtype: FastRouterContract
|
136
|
+
'''
|
137
|
+
|
138
|
+
# Map each route in the routes dictionary.
|
139
|
+
return super().map(
|
140
|
+
FastRouter,
|
141
|
+
routes=[
|
142
|
+
route.map(id=id, endpoint=f'{self.name}.{id}')
|
143
|
+
for id, route
|
144
|
+
in self.routes.items()
|
145
|
+
]
|
146
|
+
)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
"""Fast API Handlers."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
# ** infra
|
9
|
+
from tiferet.commands import raise_error
|
10
|
+
|
11
|
+
# ** app
|
12
|
+
from ..contracts import (
|
13
|
+
FastRouterContract,
|
14
|
+
FastRouteContract,
|
15
|
+
FastApiRepository
|
16
|
+
)
|
17
|
+
|
18
|
+
# *** handlers
|
19
|
+
|
20
|
+
# ** handler: fast_api_handler
|
21
|
+
class FastApiHandler(object):
|
22
|
+
'''
|
23
|
+
A handler for managing Fast API entities.
|
24
|
+
'''
|
25
|
+
|
26
|
+
# * init
|
27
|
+
def __init__(self, fast_repo: FastApiRepository):
|
28
|
+
'''
|
29
|
+
Initialize the FastApiHandler with the given repository.
|
30
|
+
|
31
|
+
:param fast_repo: An instance of FastApiRepository.
|
32
|
+
:type fast_repo: FastApiRepository
|
33
|
+
'''
|
34
|
+
|
35
|
+
# Store the repository instance.
|
36
|
+
self.fast_repo = fast_repo
|
37
|
+
|
38
|
+
# * method: get_routers
|
39
|
+
def get_routers(self) -> List[FastRouterContract]:
|
40
|
+
'''
|
41
|
+
Retrieve all Fast API routers using the repository.
|
42
|
+
|
43
|
+
:return: A list of FastRouterContract instances.
|
44
|
+
:rtype: List[FastRouterContract]
|
45
|
+
'''
|
46
|
+
|
47
|
+
# Delegate the call to the repository.
|
48
|
+
return self.fast_repo.get_routers()
|
49
|
+
|
50
|
+
# * method: get_route
|
51
|
+
def get_route(self, endpoint: str) -> FastRouteContract:
|
52
|
+
'''
|
53
|
+
Retrieve a specific Fast API route by its router and route IDs.
|
54
|
+
|
55
|
+
:param endpoint: The endpoint in the format 'router_name.route_id'.
|
56
|
+
:type endpoint: str
|
57
|
+
:return: The corresponding FastRouteContract instance.
|
58
|
+
:rtype: FastRouteContract
|
59
|
+
'''
|
60
|
+
|
61
|
+
# Split the endpoint into router and route IDs.
|
62
|
+
router_name = None
|
63
|
+
try:
|
64
|
+
router_name, route_id = endpoint.split('.')
|
65
|
+
except ValueError:
|
66
|
+
route_id = endpoint
|
67
|
+
|
68
|
+
# Delegate the call to the repository.
|
69
|
+
route = self.fast_repo.get_route(
|
70
|
+
route_id=route_id,
|
71
|
+
router_name=router_name
|
72
|
+
)
|
73
|
+
|
74
|
+
# Raise an error if the route is not found.
|
75
|
+
if route is None:
|
76
|
+
raise_error.execute(
|
77
|
+
'FAST_ROUTE_NOT_FOUND',
|
78
|
+
f'Fast API route not found for endpoint: {endpoint}',
|
79
|
+
endpoint
|
80
|
+
)
|
81
|
+
|
82
|
+
# Return the found route.
|
83
|
+
return route
|
84
|
+
|
85
|
+
# * method: get_status_code
|
86
|
+
def get_status_code(self, error_code: str) -> int:
|
87
|
+
'''
|
88
|
+
Retrieve the HTTP status code for a given error code using the repository.
|
89
|
+
|
90
|
+
:param error_code: The error code identifier.
|
91
|
+
:type error_code: str
|
92
|
+
:return: The corresponding HTTP status code.
|
93
|
+
:rtype: int
|
94
|
+
'''
|
95
|
+
|
96
|
+
# Delegate the call to the repository.
|
97
|
+
return self.fast_repo.get_status_code(
|
98
|
+
error_code=error_code
|
99
|
+
)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
"""Fast API domain models."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** infra
|
6
|
+
from tiferet.models import (
|
7
|
+
ModelObject,
|
8
|
+
StringType,
|
9
|
+
IntegerType,
|
10
|
+
ListType,
|
11
|
+
ModelType
|
12
|
+
)
|
13
|
+
|
14
|
+
# *** models
|
15
|
+
|
16
|
+
# ** model: fast_route
|
17
|
+
class FastRoute(ModelObject):
|
18
|
+
'''
|
19
|
+
A Fast route model.
|
20
|
+
'''
|
21
|
+
|
22
|
+
# * attribute: id
|
23
|
+
id = StringType(
|
24
|
+
required=True,
|
25
|
+
metadata=dict(
|
26
|
+
description='The unique identifier of the route.'
|
27
|
+
)
|
28
|
+
)
|
29
|
+
|
30
|
+
# * attribute: endpoint
|
31
|
+
endpoint = StringType(
|
32
|
+
required=True,
|
33
|
+
metadata=dict(
|
34
|
+
description='The unique identifier of the route endpoint.'
|
35
|
+
)
|
36
|
+
)
|
37
|
+
|
38
|
+
# * attribute: path
|
39
|
+
path = StringType(
|
40
|
+
required=True,
|
41
|
+
metadata=dict(
|
42
|
+
description='The URL path as string.'
|
43
|
+
)
|
44
|
+
)
|
45
|
+
|
46
|
+
# * attribute: methods
|
47
|
+
methods = ListType(
|
48
|
+
StringType,
|
49
|
+
required=True,
|
50
|
+
metadata=dict(
|
51
|
+
description='A list of HTTP methods this rule should be limited to.'
|
52
|
+
)
|
53
|
+
)
|
54
|
+
|
55
|
+
# * attribute: status_code
|
56
|
+
status_code = IntegerType(
|
57
|
+
default=200,
|
58
|
+
metadata=dict(
|
59
|
+
description='The default HTTP status code for the route response.'
|
60
|
+
)
|
61
|
+
)
|
62
|
+
|
63
|
+
# ** model: fast_router
|
64
|
+
class FastRouter(ModelObject):
|
65
|
+
'''
|
66
|
+
A Flask blueprint model.
|
67
|
+
'''
|
68
|
+
|
69
|
+
# * attribute: name
|
70
|
+
name = StringType(
|
71
|
+
required=True,
|
72
|
+
metadata=dict(
|
73
|
+
description='The name of the blueprint.'
|
74
|
+
)
|
75
|
+
)
|
76
|
+
|
77
|
+
# * attribute: url_prefix
|
78
|
+
prefix = StringType(
|
79
|
+
metadata=dict(
|
80
|
+
description='The URL prefix for all routes in this blueprint.'
|
81
|
+
)
|
82
|
+
)
|
83
|
+
|
84
|
+
# * attribute: routes
|
85
|
+
routes = ListType(
|
86
|
+
ModelType(FastRoute),
|
87
|
+
default=[],
|
88
|
+
metadata=dict(
|
89
|
+
description='A list of routes associated with this blueprint.'
|
90
|
+
)
|
91
|
+
)
|
92
|
+
|
93
|
+
# * method: add_route
|
94
|
+
def add_route(self, endpoint: str, path: str, methods: list[str], status_code: int = 200, **kwargs):
|
95
|
+
'''
|
96
|
+
Add a new route to the router.
|
97
|
+
|
98
|
+
:param endpoint: The unique identifier of the route endpoint.
|
99
|
+
:type endpoint: str
|
100
|
+
:param path: The URL path as string.
|
101
|
+
:type path: str
|
102
|
+
:param methods: A list of HTTP methods this rule should be limited to.
|
103
|
+
:type methods: list[str]
|
104
|
+
:param status_code: The default HTTP status code for the route response, defaults to 200.
|
105
|
+
:type status_code: int, optional
|
106
|
+
:param kwargs: Additional keyword arguments for route configuration.
|
107
|
+
:type kwargs: dict
|
108
|
+
'''
|
109
|
+
|
110
|
+
route = ModelObject.new(
|
111
|
+
FastRoute,
|
112
|
+
id=endpoint,
|
113
|
+
endpoint=f'{self.name}.{endpoint}',
|
114
|
+
path=path,
|
115
|
+
methods=methods,
|
116
|
+
status_code=status_code
|
117
|
+
)
|
118
|
+
|
119
|
+
self.routes.append(route)
|
@@ -0,0 +1,143 @@
|
|
1
|
+
"""Fast API YAML Configuration Proxy."""
|
2
|
+
|
3
|
+
# *** imports
|
4
|
+
|
5
|
+
# ** core
|
6
|
+
from typing import List, Any
|
7
|
+
|
8
|
+
# ** infra
|
9
|
+
from tiferet import (
|
10
|
+
TiferetError,
|
11
|
+
raise_error
|
12
|
+
)
|
13
|
+
from tiferet.proxies.yaml import (
|
14
|
+
YamlConfigurationProxy
|
15
|
+
)
|
16
|
+
|
17
|
+
# ** app
|
18
|
+
from ...contracts import (
|
19
|
+
FastApiRepository,
|
20
|
+
FastRouterContract,
|
21
|
+
FastRouteContract
|
22
|
+
)
|
23
|
+
from ...data import (
|
24
|
+
FastRouterYamlData
|
25
|
+
)
|
26
|
+
|
27
|
+
# *** proxies
|
28
|
+
|
29
|
+
# ** proxy: fast_yaml_proxy
|
30
|
+
class FastYamlProxy(FastApiRepository, YamlConfigurationProxy):
|
31
|
+
'''
|
32
|
+
A YAML configuration proxy for Fast API settings.
|
33
|
+
'''
|
34
|
+
|
35
|
+
# * init
|
36
|
+
def __init__(self, fast_config_file: str):
|
37
|
+
'''
|
38
|
+
Initialize the FastYamlProxy with the given YAML file path.
|
39
|
+
|
40
|
+
:param fast_config_file: The path to the Fast API configuration YAML file.
|
41
|
+
:type fast_config_file: str
|
42
|
+
'''
|
43
|
+
|
44
|
+
# Set the configuration file within the base class.
|
45
|
+
super().__init__(fast_config_file)
|
46
|
+
|
47
|
+
# * method: load_yaml
|
48
|
+
def load_yaml(self, start_node: callable = lambda data: data, create_data: callable = lambda data: data) -> Any:
|
49
|
+
'''
|
50
|
+
Load data from the YAML configuration file.
|
51
|
+
|
52
|
+
:param start_node: The starting node in the YAML file.
|
53
|
+
:type start_node: callable
|
54
|
+
:param create_data: A callable to create data objects from the loaded data.
|
55
|
+
:type create_data: callable
|
56
|
+
:return: The loaded data.
|
57
|
+
:rtype: Any
|
58
|
+
'''
|
59
|
+
|
60
|
+
# Load the YAML file contents using the yaml config proxy.
|
61
|
+
try:
|
62
|
+
return super().load_yaml(
|
63
|
+
start_node=start_node,
|
64
|
+
create_data=create_data
|
65
|
+
)
|
66
|
+
|
67
|
+
# Raise an error if the loading fails.
|
68
|
+
except (Exception, TiferetError) as e:
|
69
|
+
raise_error.execute(
|
70
|
+
'FAST_CONFIG_LOADING_FAILED',
|
71
|
+
f'Unable to load Fast API configuration file {self.config_file}: {e}.',
|
72
|
+
self.config_file,
|
73
|
+
str(e)
|
74
|
+
)
|
75
|
+
|
76
|
+
# * method: get_routers
|
77
|
+
def get_routers(self) -> List[FastRouterContract]:
|
78
|
+
'''
|
79
|
+
Retrieve all Fast API routers from the YAML configuration.
|
80
|
+
|
81
|
+
:return: A list of FastRouterContract instances.
|
82
|
+
:rtype: List[FastRouterContract]
|
83
|
+
'''
|
84
|
+
|
85
|
+
# Load the routers section from the YAML file.
|
86
|
+
data = self.load_yaml(
|
87
|
+
create_data=lambda data: [FastRouterYamlData.from_data(
|
88
|
+
name=name,
|
89
|
+
**router
|
90
|
+
) for name, router in data.items()],
|
91
|
+
start_node=lambda d: d.get('fast', {}).get('routers', {})
|
92
|
+
)
|
93
|
+
|
94
|
+
# Map the loaded data to FastRouterContract instances.
|
95
|
+
return [router.map() for router in data]
|
96
|
+
|
97
|
+
# * method: get_route
|
98
|
+
def get_route(self, route_id: str, router_name: str = None) -> FastRouteContract:
|
99
|
+
'''
|
100
|
+
Retrieve a specific Fast API route by its router and route IDs from the YAML configuration.
|
101
|
+
|
102
|
+
:param route_id: The route identifier.
|
103
|
+
:type route_id: str
|
104
|
+
:param router_name: The name of the router (optional).
|
105
|
+
:type router_name: str
|
106
|
+
:return: The corresponding FastRouteContract instance.
|
107
|
+
:rtype: FastRouteContract
|
108
|
+
'''
|
109
|
+
|
110
|
+
# Load the routers section from the YAML file.
|
111
|
+
routers = self.get_routers()
|
112
|
+
|
113
|
+
# Search for the specified router.
|
114
|
+
for router in routers:
|
115
|
+
if router_name and router.name != router_name:
|
116
|
+
continue
|
117
|
+
|
118
|
+
# Search for the route within the router.
|
119
|
+
for route in router.routes:
|
120
|
+
if route.id == route_id:
|
121
|
+
return route
|
122
|
+
|
123
|
+
# If not found, return None.
|
124
|
+
return None
|
125
|
+
|
126
|
+
# * method: get_status_code
|
127
|
+
def get_status_code(self, error_code: str) -> int:
|
128
|
+
'''
|
129
|
+
Retrieve the HTTP status code for a given error code from the YAML configuration.
|
130
|
+
|
131
|
+
:param error_code: The error code identifier.
|
132
|
+
:type error_code: str
|
133
|
+
:return: The corresponding HTTP status code.
|
134
|
+
:rtype: int
|
135
|
+
'''
|
136
|
+
|
137
|
+
# Load the error code from the errors section of the YAML file.
|
138
|
+
data = self.load_yaml(
|
139
|
+
start_node=lambda d: d.get('fast').get('errors', {})
|
140
|
+
)
|
141
|
+
|
142
|
+
# Return the status code if found, otherwise default to 500.
|
143
|
+
return data.get(error_code, 500)
|
@@ -0,0 +1,283 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: tiferet-fast
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: An extension of the Tiferet Framework for the Fast API.
|
5
|
+
Author-email: "Andrew Shatz, Great Strength Systems" <andrew@greatstrength.me>
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/greatstrength/tiferet-fast
|
8
|
+
Project-URL: Repository, https://github.com/greatstrength/tiferet-fast
|
9
|
+
Project-URL: Download, https://github.com/greatstrength/tiferet-fast
|
10
|
+
Requires-Python: >=3.10
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: tiferet>=1.1.2
|
14
|
+
Requires-Dist: fastapi>=0.118.0
|
15
|
+
Requires-Dist: starlette-context>=0.4.0
|
16
|
+
Provides-Extra: test
|
17
|
+
Requires-Dist: pytest>=8.3.3; extra == "test"
|
18
|
+
Requires-Dist: pytest_env>=1.1.5; extra == "test"
|
19
|
+
Dynamic: license-file
|
20
|
+
|
21
|
+
# Tiferet Fast - A FastAPI Extension for the Tiferet Framework
|
22
|
+
|
23
|
+
## Introduction
|
24
|
+
|
25
|
+
Tiferet Fast elevates the Tiferet Python framework by enabling developers to build high-performance, asynchronous APIs using FastAPI, grounded in Domain-Driven Design (DDD) principles. Inspired by the concept of beauty in harmony, this extension integrates Tiferet’s command-driven architecture with FastAPI’s modern, declarative routing and automatic OpenAPI documentation. The result is a robust, modular platform for crafting scalable web services that transform complex business logic into elegant, extensible API designs.
|
26
|
+
|
27
|
+
This tutorial guides you through creating a streamlined calculator API, leveraging Tiferet’s commands and configurations while introducing FastAPI-specific interfaces and endpoints. For a deeper understanding of Tiferet’s core concepts, refer to the [Tiferet documentation](https://github.com/greatstrength/tiferet).
|
28
|
+
|
29
|
+
## Getting Started with Tiferet Fast
|
30
|
+
|
31
|
+
Begin your Tiferet Fast journey by setting up your development environment. This guide assumes familiarity with Tiferet’s core setup.
|
32
|
+
|
33
|
+
### Installing Python
|
34
|
+
|
35
|
+
Tiferet Fast requires Python 3.10 or later. Follow the detailed [Python installation instructions](https://github.com/greatstrength/tiferet?tab=readme-ov-file#installing-python) in the Tiferet README for your platform (Windows, macOS, or Linux). Verify your installation with:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
python3.10 --version
|
39
|
+
```
|
40
|
+
|
41
|
+
### Setting Up a Virtual Environment
|
42
|
+
|
43
|
+
Create a dedicated virtual environment named `tiferet_fast_app` to manage dependencies:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
# Create the Environment
|
47
|
+
# Windows
|
48
|
+
python -m venv tiferet_fast_app
|
49
|
+
|
50
|
+
# macOS/Linux
|
51
|
+
python3.10 -m venv tiferet_fast_app
|
52
|
+
|
53
|
+
# Activate the Environment
|
54
|
+
# Windows (Command Prompt)
|
55
|
+
tiferet_fast_app\Scripts\activate
|
56
|
+
|
57
|
+
# Windows (PowerShell)
|
58
|
+
.\tiferet_fast_app\Scripts\Activate.ps1
|
59
|
+
|
60
|
+
# macOS/Linux
|
61
|
+
source tiferet_fast_app/bin/activate
|
62
|
+
```
|
63
|
+
|
64
|
+
Exit the environment with `deactivate` when finished.
|
65
|
+
|
66
|
+
## Your First Calculator API
|
67
|
+
|
68
|
+
With your environment ready, install dependencies and configure the project structure to build a dynamic calculator API using Tiferet Fast.
|
69
|
+
|
70
|
+
### Installing Tiferet Fast
|
71
|
+
|
72
|
+
In your activated virtual environment, install the Tiferet Fast extension using pip:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
# Windows
|
76
|
+
pip install tiferet_fast
|
77
|
+
|
78
|
+
# macOS/Linux
|
79
|
+
pip3 install tiferet_fast
|
80
|
+
```
|
81
|
+
|
82
|
+
Note: If developing locally, replace with the appropriate local installation command.
|
83
|
+
|
84
|
+
### Project Structure
|
85
|
+
|
86
|
+
Adapt Tiferet’s project structure to incorporate FastAPI, adding a dedicated API script:
|
87
|
+
|
88
|
+
```plaintext
|
89
|
+
project_root/
|
90
|
+
├── basic_calc.py
|
91
|
+
├── calc_cli.py
|
92
|
+
├── calc_fast_api.py
|
93
|
+
├── app/
|
94
|
+
├── commands/
|
95
|
+
│ ├── __init__.py
|
96
|
+
│ ├── calc.py
|
97
|
+
│ └── settings.py
|
98
|
+
└── configs/
|
99
|
+
├── __init__.py
|
100
|
+
├── app.yml
|
101
|
+
├── container.yml
|
102
|
+
├── error.yml
|
103
|
+
├── feature.yml
|
104
|
+
├── fast.yml
|
105
|
+
└── logging.yml
|
106
|
+
```
|
107
|
+
|
108
|
+
The `app/commands/` and `app/configs/` directories align with Tiferet’s structure (see [Tiferet README](https://github.com/greatstrength/tiferet?tab=readme-ov-file#project-structure)). The `calc_fast_api.py` script initializes and runs the FastAPI application, while `fast.yml` defines router and routing configurations.
|
109
|
+
|
110
|
+
## Crafting the Calculator API
|
111
|
+
|
112
|
+
Extend Tiferet’s calculator application into a powerful API by reusing its commands and configurations, enhanced with FastAPI-specific functionality.
|
113
|
+
|
114
|
+
### Defining Base and Arithmetic Command Classes
|
115
|
+
|
116
|
+
Leverage Tiferet’s `BasicCalcCommand` (`app/commands/settings.py`) for input validation and arithmetic commands (`AddNumber`, `SubtractNumber`, `MultiplyNumber`, `DivideNumber`, `ExponentiateNumber` in `app/commands/calc.py`) for core operations. These remain unchanged from the original calculator app; refer to the [Tiferet README](https://github.com/greatstrength/tiferet?tab=readme-ov-file#defining-base-and-arithmetic-command-classes) for details.
|
117
|
+
|
118
|
+
### Configuring the Calculator API
|
119
|
+
|
120
|
+
Reuse Tiferet’s `container.yml` ([here](https://github.com/greatstrength/tiferet?tab=readme-ov-file#configuring-the-container-in-configscontaineryml)), `error.yml` ([here](https://github.com/greatstrength/tiferet?tab=readme-ov-file#configuring-the-errors-in-configserroryml)), and `feature.yml` ([here](https://github.com/greatstrength/tiferet?tab=readme-ov-file#configuring-the-features-in-configsfeatureyml)) for command mappings, error handling, and feature workflows. Introduce a FastAPI-specific interface in `app.yml` and routing configurations in `fast.yml`.
|
121
|
+
|
122
|
+
#### Configuring the App Interface in `configs/app.yml`
|
123
|
+
|
124
|
+
Enhance `app/configs/app.yml` with the `calc_fast_api` interface:
|
125
|
+
|
126
|
+
```yaml
|
127
|
+
interfaces:
|
128
|
+
calc_fast_api:
|
129
|
+
name: Basic Calculator API
|
130
|
+
description: Perform basic calculator operations via FastAPI
|
131
|
+
module_path: tiferet_fast.contexts.fast
|
132
|
+
class_name: FastApiContext
|
133
|
+
attrs:
|
134
|
+
fast_api_handler:
|
135
|
+
module_path: tiferet_fast.handlers.fast
|
136
|
+
class_name: FastApiHandler
|
137
|
+
fast_repo:
|
138
|
+
module_path: tiferet_fast.proxies.yaml.fast
|
139
|
+
class_name: FastYamlProxy
|
140
|
+
params:
|
141
|
+
fast_config_file: app/configs/fast.yml
|
142
|
+
```
|
143
|
+
|
144
|
+
#### Configuring Routers, Routes, and Error Status Codes in `configs/fast.yml`
|
145
|
+
|
146
|
+
Define FastAPI routers, routes, and error mappings in `app/configs/fast.yml`:
|
147
|
+
|
148
|
+
```yaml
|
149
|
+
fast:
|
150
|
+
routers:
|
151
|
+
calc:
|
152
|
+
name: calc
|
153
|
+
prefix: /calc
|
154
|
+
routes:
|
155
|
+
add:
|
156
|
+
path: /add
|
157
|
+
methods: [POST, GET]
|
158
|
+
status_code: 200
|
159
|
+
subtract:
|
160
|
+
path: /subtract
|
161
|
+
methods: [POST, GET]
|
162
|
+
status_code: 200
|
163
|
+
multiply:
|
164
|
+
path: /multiply
|
165
|
+
methods: [POST, GET]
|
166
|
+
status_code: 200
|
167
|
+
divide:
|
168
|
+
path: /divide
|
169
|
+
methods: [POST, GET]
|
170
|
+
status_code: 200
|
171
|
+
sqrt:
|
172
|
+
path: /sqrt
|
173
|
+
methods: [POST, GET]
|
174
|
+
status_code: 200
|
175
|
+
errors:
|
176
|
+
DIVIDE_BY_ZERO: 400
|
177
|
+
```
|
178
|
+
|
179
|
+
The `prefix` ensures all routes are prefixed with `/calc`. Each route’s endpoint (e.g., `calc.add`) aligns with the corresponding feature ID in `feature.yml`. Routes specify a `path`, supported HTTP `methods`, and a default `status_code` of 200. The `errors` section maps error codes from `error.yml` to HTTP status codes, ensuring proper error handling.
|
180
|
+
|
181
|
+
This configuration enables `FastApiContext` to orchestrate FastAPI operations seamlessly.
|
182
|
+
|
183
|
+
### Initializing and Demonstrating the API in `calc_fast_api.py`
|
184
|
+
|
185
|
+
Create `calc_fast_api.py` to initialize the API and define endpoints:
|
186
|
+
|
187
|
+
```python
|
188
|
+
"""Tiferet Fast Calculator Initialization Script"""
|
189
|
+
|
190
|
+
# *** imports
|
191
|
+
|
192
|
+
# ** infra
|
193
|
+
from tiferet import App
|
194
|
+
from tiferet_fast.contexts.fast import FastApiContext
|
195
|
+
from starlette.requests import Request
|
196
|
+
from starlette.responses import JSONResponse
|
197
|
+
import uvicorn
|
198
|
+
|
199
|
+
# *** functions
|
200
|
+
|
201
|
+
# * function: view_func
|
202
|
+
async def view_func(request: Request):
|
203
|
+
'''
|
204
|
+
Call the view function whenever a route endpoint is invoked.
|
205
|
+
|
206
|
+
:param context: The Fast API context.
|
207
|
+
:type context: FastApiContext
|
208
|
+
:param kwargs: Additional keyword arguments.
|
209
|
+
:type kwargs: dict
|
210
|
+
:return: The result of the view function.
|
211
|
+
:rtype: Any
|
212
|
+
'''
|
213
|
+
|
214
|
+
data = await request.json() if request.headers.get('content-type') == 'application/json' else {}
|
215
|
+
data.update(dict(request.query_params))
|
216
|
+
data.update(dict(request.path_params))
|
217
|
+
|
218
|
+
# Format header data from the request headers.
|
219
|
+
headers = dict(request.headers)
|
220
|
+
|
221
|
+
# Execute the feature from the request endpoint.
|
222
|
+
response, status_code = context.run(
|
223
|
+
feature_id=request.scope['route'].name,
|
224
|
+
headers=headers,
|
225
|
+
data=data,
|
226
|
+
)
|
227
|
+
|
228
|
+
# Return the response.
|
229
|
+
return JSONResponse(response, status_code=status_code)
|
230
|
+
|
231
|
+
# *** exec
|
232
|
+
|
233
|
+
# Create the Fast API context.
|
234
|
+
context: FastApiContext = App().load_interface('calc_fast_api')
|
235
|
+
|
236
|
+
# Build the FastAPI app.
|
237
|
+
context.build_fast_app(view_func=view_func)
|
238
|
+
|
239
|
+
# Define the fast_app for external use (e.g., for FastAPI CLI or ASGI servers).
|
240
|
+
def fast_app():
|
241
|
+
'''
|
242
|
+
Create and return the FastAPI app for testing.
|
243
|
+
|
244
|
+
:return: The FastAPI app.
|
245
|
+
:rtype: FastAPI
|
246
|
+
'''
|
247
|
+
|
248
|
+
return context.fast_app
|
249
|
+
|
250
|
+
# Run the FastAPI app if this script is executed directly.
|
251
|
+
if __name__ == '__main__':
|
252
|
+
uvicorn.run(context.fast_app, host='127.0.0.1', port=8000)
|
253
|
+
```
|
254
|
+
|
255
|
+
This script initializes the Tiferet application, loads the `calc_fast_api` interface, and dynamically handles RESTful endpoints for arithmetic operations using FastAPI’s asynchronous routing.
|
256
|
+
|
257
|
+
### Demonstrating the Calculator API
|
258
|
+
|
259
|
+
Launch the API:
|
260
|
+
|
261
|
+
```bash
|
262
|
+
python3 calc_fast_api.py
|
263
|
+
```
|
264
|
+
|
265
|
+
Test endpoints using curl or tools like Postman:
|
266
|
+
|
267
|
+
```bash
|
268
|
+
# Add two numbers
|
269
|
+
curl -X POST http://127.0.0.1:8000/calc/add -H "Content-Type: application/json" -d '{"a": 1, "b": 2}'
|
270
|
+
# Output: 3
|
271
|
+
|
272
|
+
# Calculate square root
|
273
|
+
curl -X POST http://127.0.0.1:8000/calc/sqrt -H "Content-Type: application/json" -d '{"a": 16}'
|
274
|
+
# Output: 4.0
|
275
|
+
|
276
|
+
# Division by zero
|
277
|
+
curl -X POST http://127.0.0.1:8000/calc/divide -H "Content-Type: application/json" -d '{"a": 5, "b": 0}'
|
278
|
+
# Output: {"error_code": "DIVIDE_BY_ZERO", "text": "Cannot divide by zero"}
|
279
|
+
```
|
280
|
+
|
281
|
+
## Conclusion
|
282
|
+
|
283
|
+
Tiferet Fast empowers developers to craft high-performance, asynchronous FastAPI applications within Tiferet’s DDD framework, as demonstrated in this calculator tutorial. By reusing Tiferet’s commands and configurations and introducing a FastAPI interface, you’ve built a scalable, intuitive API with minimal effort. Expand its capabilities by integrating authentication, advanced features like trigonometric operations, or combining with Tiferet’s CLI or TUI contexts. Explore the [Tiferet documentation](https://github.com/greatstrength/tiferet) for advanced DDD techniques, and experiment with `app/configs/` to customize your API, transforming complex web applications into clear, purposeful solutions.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
tiferet_fast/__init__.py,sha256=Xiq4CgSuRQxcLXwiuIXiogK7J5_IaaviMAkfdehd_R0,291
|
2
|
+
tiferet_fast/contexts/__init__.py,sha256=F8zAxiH0zHtx4RkyISgfao6NKawMOhJEfO8jxa4m34U,129
|
3
|
+
tiferet_fast/contexts/fast.py,sha256=SvUFIRnN5--tTOPbCW_tf7mmeenz7inFFeYRU-7WYNY,6403
|
4
|
+
tiferet_fast/contexts/request.py,sha256=e7wH8tjtwBaP8Lvn01B_mEYdslcBDIjfqVXxXwXOW3I,1911
|
5
|
+
tiferet_fast/contracts/__init__.py,sha256=JHEX7ZauPjYr85bZ2hPwXSESL58OWS-19Vle5miAYds,153
|
6
|
+
tiferet_fast/contracts/fast.py,sha256=wj7PTnpqaX2V3XLjEUZ1fqrctUSKXxaso2Zbnft-UDQ,2133
|
7
|
+
tiferet_fast/data/__init__.py,sha256=bPQ_D1Q4XWCsus05kapNCLM3VY0gnqmkPAel325W46Q,153
|
8
|
+
tiferet_fast/data/fast.py,sha256=wuJYKAYUKHO6M3MTs8PVnR2L6dGJFQ3loqvcKR4wcjg,3633
|
9
|
+
tiferet_fast/handlers/__init__.py,sha256=pJWGC5uvM8tcSYbibQfIbWEZYytibmjbCOXT-e8tIb0,97
|
10
|
+
tiferet_fast/handlers/fast.py,sha256=E7Fu19AwfxcCwiAjBW6r2wTuZo3C6OcpJ_8F0XW3eOw,2645
|
11
|
+
tiferet_fast/models/__init__.py,sha256=ZCW9wqQ2q6AtTye2rsjjkefMw7rAICG74oI2CFQgpZQ,106
|
12
|
+
tiferet_fast/models/fast.py,sha256=3PAxhfyNIpZKiXybY-3YROU0dIGQ--l0eNWOjAIiizk,2836
|
13
|
+
tiferet_fast/proxies/__init__.py,sha256=cEj1-XgVWIFdDZW3fj9HJI82dVmzc9E0-DAXolOGdGY,44
|
14
|
+
tiferet_fast/proxies/yaml/__init__.py,sha256=WkdGmW8qmFJx65kv67h7r5i3zbioOpTul6uwcn9GRqo,100
|
15
|
+
tiferet_fast/proxies/yaml/fast.py,sha256=WMf6hZtRKBbSsbtjAia_LtuuMHADy4MT_mUBCFazvnc,4333
|
16
|
+
tiferet_fast-0.1.0.dist-info/licenses/LICENSE,sha256=1wptiB9OGy7aH1XHHktyLpobVyxc_Ek2yE5gYpP1VOE,1079
|
17
|
+
tiferet_fast-0.1.0.dist-info/METADATA,sha256=4h0Zc9z9upcvZ5I9qw2c9zAKX4I_lHhPXckBSJ3jMrU,10675
|
18
|
+
tiferet_fast-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
19
|
+
tiferet_fast-0.1.0.dist-info/top_level.txt,sha256=hSa7q81duh97rZo4WR-Ke9W22oK1doNn38WNBPObRWg,13
|
20
|
+
tiferet_fast-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Great Strength Systems
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
tiferet_fast
|