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.
@@ -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,7 @@
1
+ """Fast API Context Exports."""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .request import FastRequestContext
7
+ from .fast import FastApiContext
@@ -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,10 @@
1
+ """Tiferet Fast Contract Exports."""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .fast import (
7
+ FastRouteContract,
8
+ FastRouterContract,
9
+ FastApiRepository
10
+ )
@@ -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,10 @@
1
+ """Fast API Data Transfer Object Exports"""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .fast import (
7
+ DataObject,
8
+ FastRouteYamlData,
9
+ FastRouterYamlData
10
+ )
@@ -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,8 @@
1
+ """Fast API Handler Exports"""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .fast import (
7
+ FastApiHandler,
8
+ )
@@ -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,9 @@
1
+ """Fast API Model Exports"""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .fast import (
7
+ FastRoute,
8
+ FastRouter
9
+ )
@@ -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,3 @@
1
+ """Fast API Proxy Exports"""
2
+
3
+ # *** exports
@@ -0,0 +1,8 @@
1
+ """Fast API YAML Proxy Exports"""
2
+
3
+ # *** exports
4
+
5
+ # ** app
6
+ from .fast import (
7
+ FastYamlProxy,
8
+ )
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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