fastapi-factory-utilities 0.1.0__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of fastapi-factory-utilities might be problematic. Click here for more details.
- fastapi_factory_utilities/core/api/v1/sys/health.py +56 -12
- fastapi_factory_utilities/core/api/v1/sys/readiness.py +19 -12
- fastapi_factory_utilities/core/app/__init__.py +7 -12
- fastapi_factory_utilities/core/app/application.py +133 -0
- fastapi_factory_utilities/core/app/builder.py +123 -0
- fastapi_factory_utilities/core/app/config.py +164 -0
- fastapi_factory_utilities/core/app/exceptions.py +20 -0
- fastapi_factory_utilities/core/app/fastapi_builder.py +85 -0
- fastapi_factory_utilities/core/app/plugin_manager/__init__.py +15 -0
- fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +33 -0
- fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +190 -0
- fastapi_factory_utilities/core/exceptions.py +43 -0
- fastapi_factory_utilities/core/plugins/__init__.py +21 -0
- fastapi_factory_utilities/core/plugins/example/__init__.py +31 -0
- fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +31 -0
- fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +74 -17
- fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +27 -35
- fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -3
- fastapi_factory_utilities/core/plugins/odm_plugin/depends.py +30 -0
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +5 -5
- fastapi_factory_utilities/core/plugins/opentelemetry_plugin/builder.py +7 -7
- fastapi_factory_utilities/core/protocols.py +19 -16
- fastapi_factory_utilities/core/services/status/__init__.py +14 -0
- fastapi_factory_utilities/core/services/status/enums.py +30 -0
- fastapi_factory_utilities/core/services/status/exceptions.py +27 -0
- fastapi_factory_utilities/core/services/status/health_calculator_strategies.py +48 -0
- fastapi_factory_utilities/core/services/status/readiness_calculator_strategies.py +41 -0
- fastapi_factory_utilities/core/services/status/services.py +218 -0
- fastapi_factory_utilities/core/services/status/types.py +128 -0
- fastapi_factory_utilities/core/utils/configs.py +1 -1
- fastapi_factory_utilities/core/utils/status.py +71 -0
- fastapi_factory_utilities/core/utils/uvicorn.py +7 -8
- fastapi_factory_utilities/example/__init__.py +3 -3
- fastapi_factory_utilities/example/api/books/routes.py +7 -10
- fastapi_factory_utilities/example/app.py +50 -0
- fastapi_factory_utilities/example/application.yaml +5 -9
- fastapi_factory_utilities/example/services/books/__init__.py +2 -2
- fastapi_factory_utilities/example/services/books/services.py +9 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/METADATA +6 -4
- fastapi_factory_utilities-0.2.0.dist-info/RECORD +70 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/WHEEL +1 -1
- fastapi_factory_utilities/core/app/base/__init__.py +0 -17
- fastapi_factory_utilities/core/app/base/application.py +0 -123
- fastapi_factory_utilities/core/app/base/config_abstract.py +0 -78
- fastapi_factory_utilities/core/app/base/exceptions.py +0 -25
- fastapi_factory_utilities/core/app/base/fastapi_application_abstract.py +0 -88
- fastapi_factory_utilities/core/app/base/plugins_manager_abstract.py +0 -136
- fastapi_factory_utilities/example/app/__init__.py +0 -6
- fastapi_factory_utilities/example/app/app.py +0 -37
- fastapi_factory_utilities/example/app/config.py +0 -12
- fastapi_factory_utilities-0.1.0.dist-info/RECORD +0 -58
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/LICENSE +0 -0
- {fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Provides status services."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
from fastapi import Request
|
|
6
|
+
from reactivex import Subject
|
|
7
|
+
from structlog.stdlib import BoundLogger, get_logger
|
|
8
|
+
|
|
9
|
+
from .enums import ComponentTypeEnum, HealthStatusEnum, ReadinessStatusEnum
|
|
10
|
+
from .exceptions import ComponentRegistrationError
|
|
11
|
+
from .health_calculator_strategies import (
|
|
12
|
+
HealthCalculatorStrategy,
|
|
13
|
+
HealthSimpleCalculatorStrategy,
|
|
14
|
+
)
|
|
15
|
+
from .readiness_calculator_strategies import (
|
|
16
|
+
ReadinessCalculatorStrategy,
|
|
17
|
+
ReadinessSimpleCalculatorStrategy,
|
|
18
|
+
)
|
|
19
|
+
from .types import (
|
|
20
|
+
ComponentInstanceKey,
|
|
21
|
+
ComponentInstanceType,
|
|
22
|
+
Status,
|
|
23
|
+
StatusUpdateEvent,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger: BoundLogger = get_logger(__package__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StatusService:
|
|
30
|
+
"""Status service.
|
|
31
|
+
|
|
32
|
+
It's responsible for managing the status of the components and determine the health and readiness status of the
|
|
33
|
+
application.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
health_calculator_strategy: type[HealthCalculatorStrategy] = HealthSimpleCalculatorStrategy,
|
|
39
|
+
readiness_calculator_strategy: type[ReadinessCalculatorStrategy] = ReadinessSimpleCalculatorStrategy,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the status service."""
|
|
42
|
+
# Status
|
|
43
|
+
self._health_status: HealthStatusEnum = HealthStatusEnum.HEALTHY
|
|
44
|
+
self._health_calculator_strategy: type[HealthCalculatorStrategy] = health_calculator_strategy
|
|
45
|
+
self._readiness_status: ReadinessStatusEnum = ReadinessStatusEnum.NOT_READY
|
|
46
|
+
self._readiness_calculator_strategy: type[ReadinessCalculatorStrategy] = readiness_calculator_strategy
|
|
47
|
+
# Components
|
|
48
|
+
self._components: dict[str, ComponentInstanceType] = {}
|
|
49
|
+
self._components_status: dict[ComponentInstanceKey, Status] = {}
|
|
50
|
+
self._components_subjects: dict[ComponentInstanceKey, Subject[Status]] = {}
|
|
51
|
+
# Observers
|
|
52
|
+
self._status_subject: Subject[StatusUpdateEvent] = Subject()
|
|
53
|
+
|
|
54
|
+
def _compute_status(self) -> None:
|
|
55
|
+
"""Compute the status."""
|
|
56
|
+
# Health
|
|
57
|
+
previous_health: HealthStatusEnum = self._health_status
|
|
58
|
+
new_health: HealthStatusEnum = self._health_calculator_strategy(
|
|
59
|
+
components_status=self._components_status
|
|
60
|
+
).calculate()
|
|
61
|
+
# Readiness
|
|
62
|
+
previous_readiness: ReadinessStatusEnum = self._readiness_status
|
|
63
|
+
new_readiness: ReadinessStatusEnum = self._readiness_calculator_strategy(
|
|
64
|
+
components_status=self._components_status
|
|
65
|
+
).calculate()
|
|
66
|
+
# Update the status if needed
|
|
67
|
+
if previous_health != new_health or previous_readiness != new_readiness:
|
|
68
|
+
logger.info(
|
|
69
|
+
"Status updated: health=%s, readiness=%s",
|
|
70
|
+
new_health,
|
|
71
|
+
new_readiness,
|
|
72
|
+
)
|
|
73
|
+
self._health_status = new_health
|
|
74
|
+
self._readiness_status = new_readiness
|
|
75
|
+
self._status_subject.on_next(
|
|
76
|
+
StatusUpdateEvent(
|
|
77
|
+
health_status=new_health,
|
|
78
|
+
previous_health_status=previous_health,
|
|
79
|
+
readiness_status=new_readiness,
|
|
80
|
+
previous_readiness_status=previous_readiness,
|
|
81
|
+
triggered_at=datetime.datetime.now(),
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def get_status(self) -> Status:
|
|
86
|
+
"""Get the status.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Status: The status.
|
|
90
|
+
"""
|
|
91
|
+
return Status(
|
|
92
|
+
health=self._health_status,
|
|
93
|
+
readiness=self._readiness_status,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get_components_status_by_type(self) -> dict[ComponentTypeEnum, dict[ComponentInstanceKey, Status]]:
|
|
97
|
+
"""Get the components status.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
dict[str, dict[str, Status]]: The components status.
|
|
101
|
+
"""
|
|
102
|
+
result: dict[ComponentTypeEnum, dict[ComponentInstanceKey, Status]] = {}
|
|
103
|
+
for component_instance in self._components.values():
|
|
104
|
+
component_type: ComponentTypeEnum = component_instance.component_type
|
|
105
|
+
if component_type not in result:
|
|
106
|
+
result[component_type] = {}
|
|
107
|
+
result[component_type][component_instance.key] = self._components_status[component_instance.key]
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
def _on_next_for_component_instance(self, component_instance: ComponentInstanceType, event: Status) -> None:
|
|
111
|
+
"""On next subscribe for all component instances updates.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
115
|
+
event (Status): The status event.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ComponentRegistrationError: If the component instance is not registered.
|
|
119
|
+
"""
|
|
120
|
+
# Check if the component instance is registered
|
|
121
|
+
if component_instance.key not in self._components_status:
|
|
122
|
+
raise ComponentRegistrationError(component_instance=component_instance)
|
|
123
|
+
|
|
124
|
+
# Update the component instance status
|
|
125
|
+
self._components_status[component_instance.key] = Status(
|
|
126
|
+
health=event["health"],
|
|
127
|
+
readiness=event["readiness"],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Compute the status
|
|
131
|
+
self._compute_status()
|
|
132
|
+
|
|
133
|
+
def _register_component_instance_internaly(self, component_instance: ComponentInstanceType) -> None:
|
|
134
|
+
"""Register the component instance internally.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ComponentRegistrationError: If the component instance is already registered.
|
|
141
|
+
"""
|
|
142
|
+
# Check if the component instance is already registered
|
|
143
|
+
if component_instance.key in self._components:
|
|
144
|
+
raise ComponentRegistrationError(component_instance=component_instance)
|
|
145
|
+
# Register the component instance
|
|
146
|
+
self._components[component_instance.key] = component_instance
|
|
147
|
+
# Register the component instance status
|
|
148
|
+
self._components_status[component_instance.key] = Status(
|
|
149
|
+
health=HealthStatusEnum.HEALTHY,
|
|
150
|
+
readiness=ReadinessStatusEnum.NOT_READY,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _create_and_subscribe_to_component_instance_subject(
|
|
154
|
+
self, component_instance: ComponentInstanceType
|
|
155
|
+
) -> Subject[Status]:
|
|
156
|
+
"""Create and subscribe to the component instance subject.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Subject[Status]: The observer.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ComponentRegistrationError: If the component instance is already registered.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
# Setup the subject and subscribe to the subject
|
|
169
|
+
def on_next(event: Status) -> None:
|
|
170
|
+
"""Currying the on_next method for the component instance and delegate to the service method."""
|
|
171
|
+
self._on_next_for_component_instance(
|
|
172
|
+
component_instance=component_instance,
|
|
173
|
+
event=event,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
subject: Subject[Status] = Subject()
|
|
177
|
+
# Check if the component instance is already registered
|
|
178
|
+
if component_instance.key in self._components_subjects:
|
|
179
|
+
raise ComponentRegistrationError(component_instance=component_instance)
|
|
180
|
+
# Register the component instance subject
|
|
181
|
+
self._components_subjects[component_instance.key] = subject
|
|
182
|
+
subject.subscribe(on_next=on_next)
|
|
183
|
+
|
|
184
|
+
return subject
|
|
185
|
+
|
|
186
|
+
def register_component_instance(
|
|
187
|
+
self,
|
|
188
|
+
component_instance: ComponentInstanceType,
|
|
189
|
+
) -> Subject[Status]:
|
|
190
|
+
"""Register the component instance to the status service.
|
|
191
|
+
|
|
192
|
+
It will create a subject for the component instance to share status update and
|
|
193
|
+
the status service will subscribe automatically to it.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Subject[Status]: The observer.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ComponentRegistrationError: If the component instance is already registered.
|
|
203
|
+
"""
|
|
204
|
+
self._register_component_instance_internaly(component_instance=component_instance)
|
|
205
|
+
subject: Subject[Status] = self._create_and_subscribe_to_component_instance_subject(
|
|
206
|
+
component_instance=component_instance
|
|
207
|
+
)
|
|
208
|
+
logger.debug(
|
|
209
|
+
"Component instance registered to the status service key=%s",
|
|
210
|
+
component_instance.key,
|
|
211
|
+
component_instance=component_instance,
|
|
212
|
+
)
|
|
213
|
+
return subject
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def depends_status_service(request: Request) -> StatusService:
|
|
217
|
+
"""Get the status service, through fastapi depends."""
|
|
218
|
+
return request.app.state.status_service
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Provides the status types for the service."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import ClassVar, NewType, TypedDict, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
8
|
+
from .enums import ComponentTypeEnum, HealthStatusEnum, ReadinessStatusEnum
|
|
9
|
+
|
|
10
|
+
ComponentInstanceKey = NewType("ComponentInstanceKey", str)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComponentInstanceType:
|
|
14
|
+
"""Component instance type.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
component_type (ComponentTypeEnum): The component type.
|
|
18
|
+
identifier (str | None): The identifier.
|
|
19
|
+
key (ComponentInstanceKey): The key based on the component type and identifier.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def _generate_key(self) -> ComponentInstanceKey:
|
|
23
|
+
"""Generate the key identifier for the component instance.
|
|
24
|
+
|
|
25
|
+
It is based on the component type and identifier.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: The key.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
key: str = (
|
|
32
|
+
f"{self._component_type.value}:{self._identifier}" if self._identifier else self._component_type.value
|
|
33
|
+
)
|
|
34
|
+
return cast(ComponentInstanceKey, key)
|
|
35
|
+
|
|
36
|
+
def __init__(self, component_type: ComponentTypeEnum, identifier: str | None = None) -> None:
|
|
37
|
+
"""Initialize the component instance type.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
component_type (ComponentTypeEnum): The component type.
|
|
41
|
+
identifier (str, optional): The identifier. Defaults to None.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
self._component_type: ComponentTypeEnum = component_type
|
|
45
|
+
self._identifier: str | None = identifier
|
|
46
|
+
self._key: ComponentInstanceKey = self._generate_key()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def component_type(self) -> ComponentTypeEnum:
|
|
50
|
+
"""Get the component type.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
ComponentTypeEnum: The component type.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
return self._component_type
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def identifier(self) -> str | None:
|
|
60
|
+
"""Get the identifier.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
str | None: The identifier.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
return self._identifier
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def key(self) -> ComponentInstanceKey:
|
|
70
|
+
"""Get the key.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: The key.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
return self._key
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Status(TypedDict):
|
|
80
|
+
"""Status type.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
health (HealthStatusEnum): The health status.
|
|
84
|
+
readiness (ReadinessStatusEnum): The readiness status.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
health: HealthStatusEnum
|
|
88
|
+
readiness: ReadinessStatusEnum
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StatusUpdateEvent(BaseModel):
|
|
92
|
+
"""Status update event."""
|
|
93
|
+
|
|
94
|
+
# Pydantic config
|
|
95
|
+
model_config: ClassVar[ConfigDict] = {"frozen": True}
|
|
96
|
+
|
|
97
|
+
# Health status
|
|
98
|
+
health_status: HealthStatusEnum
|
|
99
|
+
previous_health_status: HealthStatusEnum
|
|
100
|
+
|
|
101
|
+
# Readiness status
|
|
102
|
+
readiness_status: ReadinessStatusEnum
|
|
103
|
+
previous_readiness_status: ReadinessStatusEnum
|
|
104
|
+
|
|
105
|
+
# Timestamp
|
|
106
|
+
triggered_at: datetime.datetime
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ComponentInstanceStatusUpdateEvent(BaseModel):
|
|
110
|
+
"""Component instance status update event."""
|
|
111
|
+
|
|
112
|
+
# Pydantic config
|
|
113
|
+
model_config: ClassVar[ConfigDict] = {
|
|
114
|
+
"frozen": True,
|
|
115
|
+
"arbitrary_types_allowed": True, # Needed for the ComponentInstanceType
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Component instance
|
|
119
|
+
component_instance: ComponentInstanceType
|
|
120
|
+
|
|
121
|
+
# Health status
|
|
122
|
+
health_status: HealthStatusEnum
|
|
123
|
+
|
|
124
|
+
# Readiness status
|
|
125
|
+
readiness_status: ReadinessStatusEnum
|
|
126
|
+
|
|
127
|
+
# Timestamp
|
|
128
|
+
triggered_at: datetime.datetime
|
|
@@ -41,7 +41,7 @@ def build_config_from_file_in_package(
|
|
|
41
41
|
package_name: str,
|
|
42
42
|
filename: str,
|
|
43
43
|
config_class: type[GenericConfigBaseModelType],
|
|
44
|
-
yaml_base_key: str,
|
|
44
|
+
yaml_base_key: str | None = None,
|
|
45
45
|
) -> GenericConfigBaseModelType:
|
|
46
46
|
"""Build a configuration object from a file in a package.
|
|
47
47
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Provides the monitored abstract class for monitoring the status of the application.
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
# Example of using the MonitoredAbstract
|
|
5
|
+
|
|
6
|
+
class MyMonitored(MonitoredAbstract):
|
|
7
|
+
def __init__(self, status_service: StatusService) -> None:
|
|
8
|
+
super().__init__(
|
|
9
|
+
component_instance=ComponentInstanceType(
|
|
10
|
+
component_type=ComponentTypeEnum.APPLICATION,
|
|
11
|
+
component_name="MyMonitored",
|
|
12
|
+
),
|
|
13
|
+
status_service=status_service,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def my_method(self) -> None:
|
|
17
|
+
self.update_monitoring_status(
|
|
18
|
+
Status(
|
|
19
|
+
status_type=StatusTypeEnum.INFO,
|
|
20
|
+
status_message="My method is running.",
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from abc import ABC
|
|
28
|
+
|
|
29
|
+
from reactivex import Subject
|
|
30
|
+
|
|
31
|
+
from fastapi_factory_utilities.core.services.status import (
|
|
32
|
+
ComponentInstanceType,
|
|
33
|
+
ComponentTypeEnum,
|
|
34
|
+
Status,
|
|
35
|
+
StatusService,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MonitoredAbstract(ABC):
|
|
40
|
+
"""Monitored abstract class."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, component_instance: ComponentInstanceType, status_service: StatusService) -> None:
|
|
43
|
+
"""Initialize the monitored.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
component_instance (ComponentInstanceType): The component instance.
|
|
47
|
+
status_service (StatusService): The status service.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
self._monit_component_instance: ComponentInstanceType = component_instance
|
|
51
|
+
self._monit_status_service_subject: Subject[Status] = status_service.register_component_instance(
|
|
52
|
+
component_instance=component_instance
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def update_monitoring_status(self, status: Status) -> None:
|
|
56
|
+
"""Update the monitoring status.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
status (Status): The status.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
self._monit_status_service_subject.on_next(status)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__: list[str] = [
|
|
66
|
+
"MonitoredAbstract",
|
|
67
|
+
"ComponentInstanceType",
|
|
68
|
+
"ComponentTypeEnum",
|
|
69
|
+
"Status",
|
|
70
|
+
"StatusService",
|
|
71
|
+
]
|
|
@@ -3,24 +3,23 @@
|
|
|
3
3
|
import uvicorn
|
|
4
4
|
import uvicorn.server
|
|
5
5
|
|
|
6
|
-
from fastapi_factory_utilities.core.protocols import
|
|
6
|
+
from fastapi_factory_utilities.core.protocols import ApplicationAbstractProtocol
|
|
7
7
|
from fastapi_factory_utilities.core.utils.log import clean_uvicorn_logger
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class UvicornUtils:
|
|
11
11
|
"""Provides utilities for Uvicorn."""
|
|
12
12
|
|
|
13
|
-
def __init__(self, app:
|
|
13
|
+
def __init__(self, app: ApplicationAbstractProtocol) -> None:
|
|
14
14
|
"""Instantiate the factory.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
17
|
app (BaseApplication): The application.
|
|
18
|
-
config (AppConfigAbstract): The application configuration.
|
|
19
18
|
|
|
20
19
|
Returns:
|
|
21
20
|
None
|
|
22
21
|
"""
|
|
23
|
-
self._app:
|
|
22
|
+
self._app: ApplicationAbstractProtocol = app
|
|
24
23
|
|
|
25
24
|
def build_uvicorn_config(self) -> uvicorn.Config:
|
|
26
25
|
"""Build the Uvicorn configuration.
|
|
@@ -30,10 +29,10 @@ class UvicornUtils:
|
|
|
30
29
|
"""
|
|
31
30
|
config = uvicorn.Config(
|
|
32
31
|
app=self._app.get_asgi_app(),
|
|
33
|
-
host=self._app.get_config().host,
|
|
34
|
-
port=self._app.get_config().port,
|
|
35
|
-
reload=self._app.get_config().reload,
|
|
36
|
-
workers=self._app.get_config().workers,
|
|
32
|
+
host=self._app.get_config().server.host,
|
|
33
|
+
port=self._app.get_config().server.port,
|
|
34
|
+
reload=self._app.get_config().development.reload,
|
|
35
|
+
workers=self._app.get_config().server.workers,
|
|
37
36
|
)
|
|
38
37
|
clean_uvicorn_logger()
|
|
39
38
|
return config
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Python Factory Example."""
|
|
2
2
|
|
|
3
|
-
from fastapi_factory_utilities.example.app import
|
|
3
|
+
from fastapi_factory_utilities.example.app import AppBuilder
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def main() -> None:
|
|
7
7
|
"""Main function."""
|
|
8
|
-
|
|
8
|
+
AppBuilder().build_and_serve()
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
__all__: list[str] = ["
|
|
11
|
+
__all__: list[str] = ["main"]
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
|
-
from fastapi import APIRouter, Depends
|
|
6
|
+
from fastapi import APIRouter, Depends
|
|
7
7
|
|
|
8
8
|
from fastapi_factory_utilities.example.entities.books import BookEntity
|
|
9
|
-
from fastapi_factory_utilities.example.
|
|
10
|
-
|
|
9
|
+
from fastapi_factory_utilities.example.services.books import (
|
|
10
|
+
BookService,
|
|
11
|
+
depends_book_service,
|
|
12
|
+
)
|
|
11
13
|
|
|
12
14
|
from .responses import BookListReponse, BookResponseModel
|
|
13
15
|
|
|
@@ -15,14 +17,9 @@ api_v1_books_router: APIRouter = APIRouter(prefix="/books")
|
|
|
15
17
|
api_v2_books_router: APIRouter = APIRouter(prefix="/books")
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def get_book_service(request: Request) -> BookService:
|
|
19
|
-
"""Provide Book Service."""
|
|
20
|
-
return BookService(book_repository=BookRepository(request.app.state.odm_client))
|
|
21
|
-
|
|
22
|
-
|
|
23
20
|
@api_v1_books_router.get(path="", response_model=BookListReponse)
|
|
24
21
|
def get_books(
|
|
25
|
-
books_service: BookService = Depends(
|
|
22
|
+
books_service: BookService = Depends(depends_book_service),
|
|
26
23
|
) -> BookListReponse:
|
|
27
24
|
"""Get all books.
|
|
28
25
|
|
|
@@ -46,7 +43,7 @@ def get_books(
|
|
|
46
43
|
@api_v1_books_router.get(path="/{book_id}", response_model=BookResponseModel)
|
|
47
44
|
def get_book(
|
|
48
45
|
book_id: UUID,
|
|
49
|
-
books_service: BookService = Depends(
|
|
46
|
+
books_service: BookService = Depends(depends_book_service),
|
|
50
47
|
) -> BookResponseModel:
|
|
51
48
|
"""Get a book.
|
|
52
49
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Provides the concrete application class."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from beanie import Document
|
|
6
|
+
|
|
7
|
+
from fastapi_factory_utilities.core.app.application import ApplicationAbstract
|
|
8
|
+
from fastapi_factory_utilities.core.app.builder import ApplicationGenericBuilder
|
|
9
|
+
from fastapi_factory_utilities.core.app.config import RootConfig
|
|
10
|
+
from fastapi_factory_utilities.core.plugins import PluginsEnum
|
|
11
|
+
from fastapi_factory_utilities.example.models.books.document import BookDocument
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AppRootConfig(RootConfig):
|
|
15
|
+
"""Application configuration class."""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class App(ApplicationAbstract):
|
|
21
|
+
"""Concrete application class."""
|
|
22
|
+
|
|
23
|
+
CONFIG_CLASS: ClassVar[type[RootConfig]] = AppRootConfig
|
|
24
|
+
|
|
25
|
+
PACKAGE_NAME: ClassVar[str] = "fastapi_factory_utilities.example"
|
|
26
|
+
|
|
27
|
+
ODM_DOCUMENT_MODELS: ClassVar[list[type[Document]]] = [BookDocument]
|
|
28
|
+
|
|
29
|
+
DEFAULT_PLUGINS_ACTIVATED: ClassVar[list[PluginsEnum]] = [PluginsEnum.OPENTELEMETRY_PLUGIN, PluginsEnum.ODM_PLUGIN]
|
|
30
|
+
|
|
31
|
+
def configure(self) -> None:
|
|
32
|
+
"""Configure the application."""
|
|
33
|
+
# Prevent circular import
|
|
34
|
+
from .api import api_router # pylint: disable=import-outside-toplevel
|
|
35
|
+
|
|
36
|
+
self.get_asgi_app().include_router(router=api_router)
|
|
37
|
+
|
|
38
|
+
async def on_startup(self) -> None:
|
|
39
|
+
"""Actions to perform on application startup."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
async def on_shutdown(self) -> None:
|
|
43
|
+
"""Actions to perform on application shutdown."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AppBuilder(ApplicationGenericBuilder[App]):
|
|
48
|
+
"""Application builder for the App application."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
application:
|
|
3
|
-
service_name:
|
|
4
|
-
service_namespace:
|
|
5
|
-
|
|
6
|
-
description: An example application for Python Factory
|
|
3
|
+
service_name: Example Application
|
|
4
|
+
service_namespace: fastapi_factory_utilities
|
|
5
|
+
description: An example application using fastapi-factory-utilities
|
|
7
6
|
version: 0.1.0
|
|
8
7
|
environment: ${ENVIRONMENT:development}
|
|
8
|
+
|
|
9
|
+
development:
|
|
9
10
|
debug: ${APPLICATION_DEBUG:false}
|
|
10
11
|
reload: ${APPLICATION_RELOAD:false}
|
|
11
12
|
|
|
12
|
-
plugins:
|
|
13
|
-
activate:
|
|
14
|
-
- opentelemetry_plugin
|
|
15
|
-
- odm_plugin
|
|
16
|
-
|
|
17
13
|
opentelemetry:
|
|
18
14
|
activate: "${OTEL_ACTIVE:false}"
|
|
19
15
|
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
from typing import ClassVar
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
|
+
from fastapi import Request
|
|
6
7
|
from opentelemetry import metrics
|
|
7
8
|
|
|
9
|
+
from fastapi_factory_utilities.core.plugins.odm_plugin.depends import (
|
|
10
|
+
depends_odm_database,
|
|
11
|
+
)
|
|
8
12
|
from fastapi_factory_utilities.core.plugins.opentelemetry_plugin.helpers import (
|
|
9
13
|
trace_span,
|
|
10
14
|
)
|
|
@@ -165,3 +169,8 @@ class BookService:
|
|
|
165
169
|
self.book_store[book.id] = book
|
|
166
170
|
|
|
167
171
|
self.METER_COUNTER_BOOK_UPDATE.add(amount=1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def depends_book_service(request: Request) -> BookService:
|
|
175
|
+
"""Provide Book Service."""
|
|
176
|
+
return BookService(book_repository=BookRepository(database=depends_odm_database(request=request)))
|
{fastapi_factory_utilities-0.1.0.dist-info → fastapi_factory_utilities-0.2.0.dist-info}/METADATA
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: fastapi_factory_utilities
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Consolidate libraries and utilities to create microservices in Python with FastAPI, Beanie, Httpx, AioPika and OpenTelemetry.
|
|
5
|
-
Home-page: https://github.com/miragecentury/fastapi_factory_utilities
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: python,fastapi,beanie,httpx,opentelemetry,microservices
|
|
8
7
|
Author: miragecentury
|
|
@@ -27,11 +26,14 @@ Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.49b1,<0.50)
|
|
|
27
26
|
Requires-Dist: opentelemetry-instrumentation-pymongo (>=0.49b2,<0.50)
|
|
28
27
|
Requires-Dist: opentelemetry-propagator-b3 (>=1.26.0,<2.0.0)
|
|
29
28
|
Requires-Dist: opentelemetry-sdk (>=1.26.0,<2.0.0)
|
|
29
|
+
Requires-Dist: pyaml (>=25.1.0,<26.0.0)
|
|
30
30
|
Requires-Dist: pydantic (>=2.8.2,<3.0.0)
|
|
31
31
|
Requires-Dist: pymongo (>=4.9.2,<4.10.0)
|
|
32
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: reactivex (>=4.0.4,<5.0.0)
|
|
33
|
+
Requires-Dist: structlog (>=24.1,<26.0)
|
|
33
34
|
Requires-Dist: typer (>=0.15.1,<0.16.0)
|
|
34
35
|
Requires-Dist: uvicorn (>=0.32.0,<0.33.0)
|
|
36
|
+
Project-URL: Homepage, https://github.com/miragecentury/fastapi_factory_utilities
|
|
35
37
|
Project-URL: Repository, https://github.com/miragecentury/fastapi_factory_utilities
|
|
36
38
|
Description-Content-Type: text/markdown
|
|
37
39
|
|