core-framework 0.3.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.
- core_framework/__init__.py +0 -0
- core_framework/alembic/comment/alembic/README +1 -0
- core_framework/alembic/comment/alembic/env.py +72 -0
- core_framework/alembic/comment/alembic/script.py.mako +28 -0
- core_framework/alembic/comment/alembic/versions/30334fd1347b_init.py +59 -0
- core_framework/alembic/comment/alembic/versions/a2b3c4d5e6f7_improve_comment_indexes.py +54 -0
- core_framework/alembic/comment/alembic/versions/bcc8e00cfc8b_add_extra_tables.py +64 -0
- core_framework/alembic/comment/alembic/versions/d1e2f3a4b5c6_add_comment_stats_dirty_table.py +29 -0
- core_framework/alembic/comment/alembic/versions/e3f4a5b6c7d8_cascade_delete_comment_descendants.py +49 -0
- core_framework/alembic/comment/alembic/versions/f7e6d5c4b3a2_comments_path_to_ltree.py +60 -0
- core_framework/alembic/comment/alembic.ini +52 -0
- core_framework/alembic/extension/alembic/README +1 -0
- core_framework/alembic/extension/alembic/env.py +98 -0
- core_framework/alembic/extension/alembic/script.py.mako +28 -0
- core_framework/alembic/extension/alembic/versions/0389226049cb_add_pg_trgm_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/5dc58b016cf5_add_citext_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/b0ba0d8a284e_add_pg_stat_statements_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/c9d0e1f2a3b4_add_ltree_extension.py +25 -0
- core_framework/alembic/extension/alembic.ini +147 -0
- core_framework/alembic/moderation/alembic/README +1 -0
- core_framework/alembic/moderation/alembic/env.py +98 -0
- core_framework/alembic/moderation/alembic/script.py.mako +28 -0
- core_framework/alembic/moderation/alembic/versions/085ba9021850_add_category_to_user_restrictions.py +93 -0
- core_framework/alembic/moderation/alembic/versions/5f9e4fc14a41_create_moderation_appeals_table.py +69 -0
- core_framework/alembic/moderation/alembic/versions/63e37381e73b_add_user_reports_table.py +33 -0
- core_framework/alembic/moderation/alembic/versions/6a2ae31b7ac6_add_moderation_actions_table.py +34 -0
- core_framework/alembic/moderation/alembic/versions/716aa1735c03_improve_indexes.py +36 -0
- core_framework/alembic/moderation/alembic/versions/7d243ddbfde1_add_post_reports_table.py +35 -0
- core_framework/alembic/moderation/alembic/versions/8fba1f72dd46_add_indexes.py +64 -0
- core_framework/alembic/moderation/alembic/versions/95cc35a51984_update_restriction_history.py +91 -0
- core_framework/alembic/moderation/alembic/versions/9ad79d0af730_add_unique_constraint_user_reports_.py +28 -0
- core_framework/alembic/moderation/alembic/versions/a5e569f5df1a_create_user_restrictions_table.py +38 -0
- core_framework/alembic/moderation/alembic/versions/b2c3d4e5f6a7_add_indexes.py +42 -0
- core_framework/alembic/moderation/alembic/versions/c3d4e5f6a7b8_improve_report_indexes.py +48 -0
- core_framework/alembic/moderation/alembic/versions/d4af74643ff5_add_internal_notes_table.py +38 -0
- core_framework/alembic/moderation/alembic/versions/db20f2fb7390_add_comment_reports_table.py +35 -0
- core_framework/alembic/moderation/alembic/versions/e66226952ea6_add_report_category_to_user_reports_.py +54 -0
- core_framework/alembic/moderation/alembic/versions/f5e8cb275c30_enforce_1_pending_appeal.py +29 -0
- core_framework/alembic/moderation/alembic/versions/fe1faad2832d_create_restriction_history_table.py +69 -0
- core_framework/alembic/moderation/alembic.ini +147 -0
- core_framework/alembic/post/alembic/README +1 -0
- core_framework/alembic/post/alembic/env.py +97 -0
- core_framework/alembic/post/alembic/script.py.mako +28 -0
- core_framework/alembic/post/alembic/versions/51542673f5c8_add_tables_for_muted_banned_users.py +41 -0
- core_framework/alembic/post/alembic/versions/5beeeae40a4a_add_post_views_table.py +45 -0
- core_framework/alembic/post/alembic/versions/620971509a8b_init.py +55 -0
- core_framework/alembic/post/alembic/versions/a1b2c3d4e5f6_add_indexes.py +44 -0
- core_framework/alembic/post/alembic/versions/c1d2e3f4a5b6_add_post_hashtags_table.py +36 -0
- core_framework/alembic/post/alembic/versions/e56723f2afff_add_post_stats_table.py +39 -0
- core_framework/alembic/post/alembic/versions/fbc723ac58cc_add_post_likes_table.py +32 -0
- core_framework/alembic/post/alembic.ini +149 -0
- core_framework/alembic/user/alembic/README +1 -0
- core_framework/alembic/user/alembic/env.py +98 -0
- core_framework/alembic/user/alembic/script.py.mako +28 -0
- core_framework/alembic/user/alembic/versions/1a8bb99726ed_remove_avatar_id_from_users.py +81 -0
- core_framework/alembic/user/alembic/versions/2ccacf455941_improve_indexes.py +34 -0
- core_framework/alembic/user/alembic/versions/47f47ce2110e_create_user_deletions_table.py +31 -0
- core_framework/alembic/user/alembic/versions/5976db3f0175_drop_user_states.py +26 -0
- core_framework/alembic/user/alembic/versions/62417002cf32_add_indexes.py +46 -0
- core_framework/alembic/user/alembic/versions/6f7ccf3c226b_refactor_user_login_events.py +66 -0
- core_framework/alembic/user/alembic/versions/73432817015b_add_user_preferences_table.py +33 -0
- core_framework/alembic/user/alembic/versions/765bc01a7a59_create_user_blocks_table.py +33 -0
- core_framework/alembic/user/alembic/versions/7a56631f9927_create_user_login_events_table.py +49 -0
- core_framework/alembic/user/alembic/versions/831611e589bc_create_user_state.py +31 -0
- core_framework/alembic/user/alembic/versions/83c98ab2a779_add_user_profiles_table.py +88 -0
- core_framework/alembic/user/alembic/versions/8a94362cad6d_create_user_role.py +31 -0
- core_framework/alembic/user/alembic/versions/94b973923895_add_user_change_history_table.py +97 -0
- core_framework/alembic/user/alembic/versions/cbc0f4efe84f_add_avatar_id_column_to_users_table.py +31 -0
- core_framework/alembic/user/alembic/versions/d8b98ac6b073_add_index_for_get_admin_user_ids_query.py +29 -0
- core_framework/alembic/user/alembic/versions/ddb70cc09d16_create_user_states_table.py +34 -0
- core_framework/alembic/user/alembic/versions/f9ba10815ecd_add_users_table.py +33 -0
- core_framework/alembic/user/alembic.ini +147 -0
- core_framework/api/__init__.py +0 -0
- core_framework/api/admin/__init__.py +0 -0
- core_framework/api/admin/comments/router.py +69 -0
- core_framework/api/admin/comments/schemas.py +53 -0
- core_framework/api/admin/moderation/__init__.py +0 -0
- core_framework/api/admin/moderation/router.py +205 -0
- core_framework/api/admin/moderation/schemas.py +110 -0
- core_framework/api/admin/posts/router.py +62 -0
- core_framework/api/admin/posts/schemas.py +29 -0
- core_framework/api/admin/router.py +17 -0
- core_framework/api/admin/users/__init__.py +0 -0
- core_framework/api/admin/users/router.py +181 -0
- core_framework/api/admin/users/schemas.py +137 -0
- core_framework/api/auth/__init__.py +0 -0
- core_framework/api/auth/router.py +21 -0
- core_framework/api/auth/schemas.py +28 -0
- core_framework/api/comments/authenticated/router.py +126 -0
- core_framework/api/comments/authenticated/schemas.py +27 -0
- core_framework/api/comments/public/router.py +103 -0
- core_framework/api/comments/public/schemas.py +36 -0
- core_framework/api/comments/router.py +9 -0
- core_framework/api/comments/schemas.py +17 -0
- core_framework/api/dependencies.py +168 -0
- core_framework/api/events/router.py +39 -0
- core_framework/api/events/schemas.py +20 -0
- core_framework/api/posts/authenticated/router.py +83 -0
- core_framework/api/posts/authenticated/schemas.py +37 -0
- core_framework/api/posts/public/router.py +100 -0
- core_framework/api/posts/public/schemas.py +39 -0
- core_framework/api/posts/router.py +9 -0
- core_framework/api/posts/schemas.py +39 -0
- core_framework/api/router.py +19 -0
- core_framework/api/schemas.py +9 -0
- core_framework/api/system/__init__.py +0 -0
- core_framework/api/system/router.py +108 -0
- core_framework/api/users/__init__.py +0 -0
- core_framework/api/users/authenticated/__init__.py +0 -0
- core_framework/api/users/authenticated/router.py +244 -0
- core_framework/api/users/authenticated/schemas.py +81 -0
- core_framework/api/users/public/__init__.py +0 -0
- core_framework/api/users/public/router.py +25 -0
- core_framework/api/users/public/schemas.py +7 -0
- core_framework/api/users/router.py +9 -0
- core_framework/api/users/shared/schemas.py +174 -0
- core_framework/application/__init__.py +0 -0
- core_framework/application/auth/__init__.py +0 -0
- core_framework/application/auth/access_service.py +26 -0
- core_framework/application/auth/auth_service.py +10 -0
- core_framework/application/auth/models.py +10 -0
- core_framework/application/bootstrap.py +19 -0
- core_framework/application/comments/admin_service.py +236 -0
- core_framework/application/comments/aggregation_service.py +28 -0
- core_framework/application/comments/authenticated_service.py +89 -0
- core_framework/application/comments/public_service.py +218 -0
- core_framework/application/events/README.md +26 -0
- core_framework/application/events/event_service.py +51 -0
- core_framework/application/events/event_token.py +46 -0
- core_framework/application/events/models.py +9 -0
- core_framework/application/moderation/__init__.py +0 -0
- core_framework/application/moderation/appeal_service.py +98 -0
- core_framework/application/moderation/moderator_service.py +46 -0
- core_framework/application/moderation/report_service.py +180 -0
- core_framework/application/moderation/scheduled_service.py +5 -0
- core_framework/application/moderation/user_service.py +180 -0
- core_framework/application/posts/admin_service.py +104 -0
- core_framework/application/posts/aggregation_service.py +28 -0
- core_framework/application/posts/authenticated_service.py +72 -0
- core_framework/application/posts/public_service.py +197 -0
- core_framework/application/shared/__init__.py +0 -0
- core_framework/application/shared/enums.py +16 -0
- core_framework/application/shared/exceptions.py +16 -0
- core_framework/application/shared/user_agent.py +24 -0
- core_framework/application/users/__init__.py +0 -0
- core_framework/application/users/admin_service.py +298 -0
- core_framework/application/users/authenticated_service.py +179 -0
- core_framework/application/users/public_service.py +7 -0
- core_framework/application/users/scheduled_service.py +5 -0
- core_framework/bundled_alembic.py +57 -0
- core_framework/core/__init__.py +37 -0
- core_framework/core/cache.py +234 -0
- core_framework/core/context.py +14 -0
- core_framework/core/database.py +111 -0
- core_framework/core/exception_handlers/__init__.py +3 -0
- core_framework/core/exception_handlers/comment.py +99 -0
- core_framework/core/exception_handlers/common.py +5 -0
- core_framework/core/exception_handlers/moderation.py +104 -0
- core_framework/core/exception_handlers/post.py +54 -0
- core_framework/core/exception_handlers/setup.py +80 -0
- core_framework/core/exception_handlers/user.py +72 -0
- core_framework/core/http_client.py +64 -0
- core_framework/core/logging.py +99 -0
- core_framework/core/middleware.py +64 -0
- core_framework/core/observability.py +36 -0
- core_framework/core/pagination.py +203 -0
- core_framework/core/redis.py +135 -0
- core_framework/core/runtime.py +66 -0
- core_framework/core/settings.py +189 -0
- core_framework/domains/__init__.py +0 -0
- core_framework/domains/comment/README.md +243 -0
- core_framework/domains/comment/__init__.py +25 -0
- core_framework/domains/comment/constants.py +3 -0
- core_framework/domains/comment/dependencies.py +29 -0
- core_framework/domains/comment/enums.py +11 -0
- core_framework/domains/comment/exceptions.py +31 -0
- core_framework/domains/comment/models.py +54 -0
- core_framework/domains/comment/repository.py +947 -0
- core_framework/domains/comment/service.py +259 -0
- core_framework/domains/moderation/README.md +138 -0
- core_framework/domains/moderation/__init__.py +47 -0
- core_framework/domains/moderation/dependencies.py +29 -0
- core_framework/domains/moderation/enums.py +62 -0
- core_framework/domains/moderation/exceptions.py +31 -0
- core_framework/domains/moderation/models.py +94 -0
- core_framework/domains/moderation/repository.py +828 -0
- core_framework/domains/moderation/service.py +334 -0
- core_framework/domains/post/README.md +182 -0
- core_framework/domains/post/__init__.py +22 -0
- core_framework/domains/post/constants.py +3 -0
- core_framework/domains/post/dependencies.py +29 -0
- core_framework/domains/post/enums.py +18 -0
- core_framework/domains/post/exceptions.py +21 -0
- core_framework/domains/post/models.py +53 -0
- core_framework/domains/post/repository.py +791 -0
- core_framework/domains/post/service.py +204 -0
- core_framework/domains/user/README.md +74 -0
- core_framework/domains/user/__init__.py +39 -0
- core_framework/domains/user/constants.py +8 -0
- core_framework/domains/user/dependencies.py +29 -0
- core_framework/domains/user/enums.py +19 -0
- core_framework/domains/user/exceptions.py +31 -0
- core_framework/domains/user/models.py +124 -0
- core_framework/domains/user/repository.py +612 -0
- core_framework/domains/user/service.py +257 -0
- core_framework/domains/user/utils.py +182 -0
- core_framework/main.py +104 -0
- core_framework/worker/__init__.py +0 -0
- core_framework/worker/main.py +56 -0
- core_framework/worker/schedules/__init__.py +35 -0
- core_framework/worker/schedules/schedule_aggregate_comment_stats.py +32 -0
- core_framework/worker/schedules/schedule_aggregate_post_view_counts.py +28 -0
- core_framework/worker/schedules/schedule_expired_account_deletions.py +24 -0
- core_framework/worker/schedules/schedule_expired_mute_lifts.py +24 -0
- core_framework/worker/tasks/__init__.py +11 -0
- core_framework/worker/tasks/process_account_deletion.py +13 -0
- core_framework/worker/tasks/process_aggregate_comment_stats.py +19 -0
- core_framework/worker/tasks/process_aggregate_post_stats.py +12 -0
- core_framework/worker/tasks/process_mute_lift.py +13 -0
- core_framework-0.3.0.dist-info/METADATA +22 -0
- core_framework-0.3.0.dist-info/RECORD +222 -0
- core_framework-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from fastapi import FastAPI, Request, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from core_framework.core.exception_handlers.common import GENERIC_INTERNAL_SERVER_ERROR_DETAIL
|
|
5
|
+
from core_framework.domains.comment import (
|
|
6
|
+
BaseCommentException,
|
|
7
|
+
CommentEditLimitReachedException,
|
|
8
|
+
CommentNotFoundException,
|
|
9
|
+
CommentUpdateNotFoundException,
|
|
10
|
+
MaxReplyDepthException,
|
|
11
|
+
ParentCommentNotFoundException,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def parent_comment_not_found_exception_handler(
|
|
16
|
+
request: Request,
|
|
17
|
+
exc: ParentCommentNotFoundException,
|
|
18
|
+
) -> JSONResponse:
|
|
19
|
+
return JSONResponse(
|
|
20
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
21
|
+
content={"detail": exc.message},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def comment_not_found_exception_handler(
|
|
26
|
+
request: Request,
|
|
27
|
+
exc: CommentNotFoundException,
|
|
28
|
+
) -> JSONResponse:
|
|
29
|
+
return JSONResponse(
|
|
30
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
31
|
+
content={"detail": exc.message},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def max_reply_depth_exception_handler(
|
|
36
|
+
request: Request,
|
|
37
|
+
exc: MaxReplyDepthException,
|
|
38
|
+
) -> JSONResponse:
|
|
39
|
+
return JSONResponse(
|
|
40
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
41
|
+
content={"detail": exc.message},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def comment_update_not_found_exception_handler(
|
|
46
|
+
request: Request,
|
|
47
|
+
exc: CommentUpdateNotFoundException,
|
|
48
|
+
) -> JSONResponse:
|
|
49
|
+
return JSONResponse(
|
|
50
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
51
|
+
content={"detail": exc.message},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def comment_edit_limit_reached_exception_handler(
|
|
56
|
+
request: Request,
|
|
57
|
+
exc: CommentEditLimitReachedException,
|
|
58
|
+
) -> JSONResponse:
|
|
59
|
+
return JSONResponse(
|
|
60
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
61
|
+
content={"detail": exc.message},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def base_comment_exception_handler(
|
|
66
|
+
request: Request,
|
|
67
|
+
exc: BaseCommentException,
|
|
68
|
+
) -> JSONResponse:
|
|
69
|
+
return JSONResponse(
|
|
70
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
71
|
+
content={"detail": GENERIC_INTERNAL_SERVER_ERROR_DETAIL},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_comment_exception_handlers(app: FastAPI) -> None:
|
|
76
|
+
app.add_exception_handler(
|
|
77
|
+
CommentNotFoundException,
|
|
78
|
+
comment_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
79
|
+
)
|
|
80
|
+
app.add_exception_handler(
|
|
81
|
+
ParentCommentNotFoundException,
|
|
82
|
+
parent_comment_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
83
|
+
)
|
|
84
|
+
app.add_exception_handler(
|
|
85
|
+
MaxReplyDepthException,
|
|
86
|
+
max_reply_depth_exception_handler, # ty: ignore[invalid-argument-type]
|
|
87
|
+
)
|
|
88
|
+
app.add_exception_handler(
|
|
89
|
+
CommentUpdateNotFoundException,
|
|
90
|
+
comment_update_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
91
|
+
)
|
|
92
|
+
app.add_exception_handler(
|
|
93
|
+
CommentEditLimitReachedException,
|
|
94
|
+
comment_edit_limit_reached_exception_handler, # ty: ignore[invalid-argument-type]
|
|
95
|
+
)
|
|
96
|
+
app.add_exception_handler(
|
|
97
|
+
BaseCommentException,
|
|
98
|
+
base_comment_exception_handler, # ty: ignore[invalid-argument-type]
|
|
99
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from fastapi import FastAPI, Request, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from core_framework.core.exception_handlers.common import GENERIC_INTERNAL_SERVER_ERROR_DETAIL
|
|
5
|
+
from core_framework.domains.moderation import (
|
|
6
|
+
AppealAlreadyDecidedException,
|
|
7
|
+
AppealNotFoundException,
|
|
8
|
+
AppealRequirementException,
|
|
9
|
+
BaseModerationException,
|
|
10
|
+
ExistingPendingAppealException,
|
|
11
|
+
SelfReportException,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def self_report_exception_handler(
|
|
16
|
+
request: Request,
|
|
17
|
+
exc: SelfReportException,
|
|
18
|
+
) -> JSONResponse:
|
|
19
|
+
return JSONResponse(
|
|
20
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
21
|
+
content={"detail": exc.message},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def appeal_requirement_exception_handler(
|
|
26
|
+
request: Request,
|
|
27
|
+
exc: AppealRequirementException,
|
|
28
|
+
) -> JSONResponse:
|
|
29
|
+
return JSONResponse(
|
|
30
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
31
|
+
content={"detail": exc.message},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def existing_pending_appeal_exception_handler(
|
|
36
|
+
request: Request,
|
|
37
|
+
exc: ExistingPendingAppealException,
|
|
38
|
+
) -> JSONResponse:
|
|
39
|
+
return JSONResponse(
|
|
40
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
41
|
+
content={"detail": exc.message},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def appeal_not_found_exception_handler(
|
|
46
|
+
request: Request,
|
|
47
|
+
exc: AppealNotFoundException,
|
|
48
|
+
) -> JSONResponse:
|
|
49
|
+
return JSONResponse(
|
|
50
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
51
|
+
content={"detail": exc.message},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def appeal_already_decided_exception_handler(
|
|
56
|
+
request: Request,
|
|
57
|
+
exc: AppealAlreadyDecidedException,
|
|
58
|
+
) -> JSONResponse:
|
|
59
|
+
return JSONResponse(
|
|
60
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
61
|
+
content={"detail": exc.message},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def base_moderation_exception_handler(
|
|
66
|
+
request: Request,
|
|
67
|
+
exc: BaseModerationException,
|
|
68
|
+
) -> JSONResponse:
|
|
69
|
+
return JSONResponse(
|
|
70
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
71
|
+
content={"detail": GENERIC_INTERNAL_SERVER_ERROR_DETAIL},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_moderation_exception_handlers(app: FastAPI) -> None:
|
|
76
|
+
app.add_exception_handler(
|
|
77
|
+
SelfReportException,
|
|
78
|
+
self_report_exception_handler, # ty: ignore[invalid-argument-type]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
app.add_exception_handler(
|
|
82
|
+
AppealRequirementException,
|
|
83
|
+
appeal_requirement_exception_handler, # ty: ignore[invalid-argument-type]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
app.add_exception_handler(
|
|
87
|
+
ExistingPendingAppealException,
|
|
88
|
+
existing_pending_appeal_exception_handler, # ty: ignore[invalid-argument-type]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
app.add_exception_handler(
|
|
92
|
+
AppealNotFoundException,
|
|
93
|
+
appeal_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
app.add_exception_handler(
|
|
97
|
+
AppealAlreadyDecidedException,
|
|
98
|
+
appeal_already_decided_exception_handler, # ty: ignore[invalid-argument-type]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
app.add_exception_handler(
|
|
102
|
+
BaseModerationException,
|
|
103
|
+
base_moderation_exception_handler, # ty: ignore[invalid-argument-type]
|
|
104
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from fastapi import FastAPI, Request, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from core_framework.core.exception_handlers.common import GENERIC_INTERNAL_SERVER_ERROR_DETAIL
|
|
5
|
+
from core_framework.domains.post import (
|
|
6
|
+
BasePostException,
|
|
7
|
+
EditLimitReachedException,
|
|
8
|
+
PostUpdateNotFoundException,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def post_update_not_found_exception_handler(
|
|
13
|
+
request: Request,
|
|
14
|
+
exc: PostUpdateNotFoundException,
|
|
15
|
+
) -> JSONResponse:
|
|
16
|
+
return JSONResponse(
|
|
17
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
18
|
+
content={"detail": exc.message},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def edit_limit_reached_exception_handler(
|
|
23
|
+
request: Request,
|
|
24
|
+
exc: EditLimitReachedException,
|
|
25
|
+
) -> JSONResponse:
|
|
26
|
+
return JSONResponse(
|
|
27
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
28
|
+
content={"detail": exc.message},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def base_post_exception_handler(
|
|
33
|
+
request: Request,
|
|
34
|
+
exc: BasePostException,
|
|
35
|
+
) -> JSONResponse:
|
|
36
|
+
return JSONResponse(
|
|
37
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
38
|
+
content={"detail": GENERIC_INTERNAL_SERVER_ERROR_DETAIL},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def add_post_exception_handlers(app: FastAPI) -> None:
|
|
43
|
+
app.add_exception_handler(
|
|
44
|
+
PostUpdateNotFoundException,
|
|
45
|
+
post_update_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
46
|
+
)
|
|
47
|
+
app.add_exception_handler(
|
|
48
|
+
EditLimitReachedException,
|
|
49
|
+
edit_limit_reached_exception_handler, # ty: ignore[invalid-argument-type]
|
|
50
|
+
)
|
|
51
|
+
app.add_exception_handler(
|
|
52
|
+
BasePostException,
|
|
53
|
+
base_post_exception_handler, # ty: ignore[invalid-argument-type]
|
|
54
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from fastapi import FastAPI, Request, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from core_framework.application.shared.exceptions import (
|
|
5
|
+
ForbiddenException,
|
|
6
|
+
UnauthorizedException,
|
|
7
|
+
UserNotFoundException,
|
|
8
|
+
)
|
|
9
|
+
from core_framework.core.exception_handlers.comment import add_comment_exception_handlers
|
|
10
|
+
from core_framework.core.exception_handlers.moderation import add_moderation_exception_handlers
|
|
11
|
+
from core_framework.core.exception_handlers.post import add_post_exception_handlers
|
|
12
|
+
from core_framework.core.exception_handlers.user import add_user_exception_handlers
|
|
13
|
+
from core_framework.domains.post import PostNotFoundException
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def unauthorized_exception_handler(
|
|
17
|
+
request: Request,
|
|
18
|
+
exc: UnauthorizedException,
|
|
19
|
+
) -> JSONResponse:
|
|
20
|
+
return JSONResponse(
|
|
21
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
22
|
+
content={"detail": exc.message},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def forbidden_exception_handler(
|
|
27
|
+
request: Request,
|
|
28
|
+
exc: ForbiddenException,
|
|
29
|
+
) -> JSONResponse:
|
|
30
|
+
return JSONResponse(
|
|
31
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
32
|
+
content={"detail": exc.message},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def user_not_found_exception_handler(
|
|
37
|
+
request: Request,
|
|
38
|
+
exc: UserNotFoundException,
|
|
39
|
+
) -> JSONResponse:
|
|
40
|
+
return JSONResponse(
|
|
41
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
42
|
+
content={"detail": exc.message},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def post_not_found_exception_handler(
|
|
47
|
+
request: Request,
|
|
48
|
+
exc: PostNotFoundException,
|
|
49
|
+
) -> JSONResponse:
|
|
50
|
+
return JSONResponse(
|
|
51
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
52
|
+
content={"detail": exc.message},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def setup_exception_handlers(app: FastAPI) -> None:
|
|
57
|
+
app.add_exception_handler(
|
|
58
|
+
UnauthorizedException,
|
|
59
|
+
unauthorized_exception_handler, # ty: ignore[invalid-argument-type]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
app.add_exception_handler(
|
|
63
|
+
ForbiddenException,
|
|
64
|
+
forbidden_exception_handler, # ty: ignore[invalid-argument-type]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
app.add_exception_handler(
|
|
68
|
+
UserNotFoundException,
|
|
69
|
+
user_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
app.add_exception_handler(
|
|
73
|
+
PostNotFoundException,
|
|
74
|
+
post_not_found_exception_handler, # ty: ignore[invalid-argument-type]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
add_user_exception_handlers(app)
|
|
78
|
+
add_comment_exception_handlers(app)
|
|
79
|
+
add_moderation_exception_handlers(app)
|
|
80
|
+
add_post_exception_handlers(app)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from fastapi import FastAPI, Request, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from core_framework.core.exception_handlers.common import GENERIC_INTERNAL_SERVER_ERROR_DETAIL
|
|
5
|
+
from core_framework.domains.user import (
|
|
6
|
+
BaseUserException,
|
|
7
|
+
SelfBlockException,
|
|
8
|
+
UserIdConflictException,
|
|
9
|
+
UsernameConflictException,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def username_conflict_exception_handler(
|
|
14
|
+
request: Request,
|
|
15
|
+
exc: UsernameConflictException,
|
|
16
|
+
) -> JSONResponse:
|
|
17
|
+
return JSONResponse(
|
|
18
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
19
|
+
content={"detail": exc.message},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def user_id_conflict_exception_handler(
|
|
24
|
+
request: Request,
|
|
25
|
+
exc: UserIdConflictException,
|
|
26
|
+
) -> JSONResponse:
|
|
27
|
+
return JSONResponse(
|
|
28
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
29
|
+
content={"detail": exc.message},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def self_block_exception_handler(
|
|
34
|
+
request: Request,
|
|
35
|
+
exc: SelfBlockException,
|
|
36
|
+
) -> JSONResponse:
|
|
37
|
+
return JSONResponse(
|
|
38
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
39
|
+
content={"detail": exc.message},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def base_user_exception_handler(
|
|
44
|
+
request: Request,
|
|
45
|
+
exc: BaseUserException,
|
|
46
|
+
) -> JSONResponse:
|
|
47
|
+
return JSONResponse(
|
|
48
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
49
|
+
content={"detail": GENERIC_INTERNAL_SERVER_ERROR_DETAIL},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_user_exception_handlers(app: FastAPI) -> None:
|
|
54
|
+
app.add_exception_handler(
|
|
55
|
+
UsernameConflictException,
|
|
56
|
+
username_conflict_exception_handler, # ty: ignore[invalid-argument-type]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
app.add_exception_handler(
|
|
60
|
+
UserIdConflictException,
|
|
61
|
+
user_id_conflict_exception_handler, # ty: ignore[invalid-argument-type]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
app.add_exception_handler(
|
|
65
|
+
SelfBlockException,
|
|
66
|
+
self_block_exception_handler, # ty: ignore[invalid-argument-type]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
app.add_exception_handler(
|
|
70
|
+
BaseUserException,
|
|
71
|
+
base_user_exception_handler, # ty: ignore[invalid-argument-type]
|
|
72
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from types import TracebackType
|
|
2
|
+
from typing import Any, Final, Self
|
|
3
|
+
|
|
4
|
+
from httpx import AsyncClient, Limits, Timeout
|
|
5
|
+
|
|
6
|
+
HTTP_CLIENT_NOT_CONNECTED_MSG: Final[str] = "HTTP client is not connected"
|
|
7
|
+
HTTP_CLIENT_ALREADY_CONNECTED_MSG: Final[str] = "HTTP client is already connected"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HttpClient:
|
|
11
|
+
__slots__ = ("_client", "_limits", "_timeout", "_event_hooks", "_headers")
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
timeout: Timeout,
|
|
16
|
+
limits: Limits,
|
|
17
|
+
event_hooks: dict[str, Any] | None = None,
|
|
18
|
+
headers: dict[str, Any] | None = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self._client: AsyncClient | None = None
|
|
21
|
+
self._timeout = timeout
|
|
22
|
+
self._limits = limits
|
|
23
|
+
self._event_hooks = event_hooks
|
|
24
|
+
self._headers = headers
|
|
25
|
+
|
|
26
|
+
async def __aenter__(self) -> Self:
|
|
27
|
+
return await self.connect()
|
|
28
|
+
|
|
29
|
+
async def __aexit__(
|
|
30
|
+
self,
|
|
31
|
+
exc_type: type[BaseException] | None,
|
|
32
|
+
exc_value: BaseException | None,
|
|
33
|
+
traceback: TracebackType | None,
|
|
34
|
+
) -> None:
|
|
35
|
+
await self.disconnect()
|
|
36
|
+
|
|
37
|
+
async def connect(self) -> Self:
|
|
38
|
+
if self._client is not None:
|
|
39
|
+
raise RuntimeError(HTTP_CLIENT_ALREADY_CONNECTED_MSG)
|
|
40
|
+
|
|
41
|
+
self._client = AsyncClient(
|
|
42
|
+
http2=True,
|
|
43
|
+
timeout=self._timeout,
|
|
44
|
+
limits=self._limits,
|
|
45
|
+
event_hooks=self._event_hooks,
|
|
46
|
+
headers=self._headers,
|
|
47
|
+
)
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
async def disconnect(self) -> None:
|
|
51
|
+
if self._client is None:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
await self._client.aclose()
|
|
56
|
+
finally:
|
|
57
|
+
self._client = None
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def client(self) -> AsyncClient:
|
|
61
|
+
if self._client is None:
|
|
62
|
+
raise RuntimeError(HTTP_CLIENT_NOT_CONNECTED_MSG)
|
|
63
|
+
|
|
64
|
+
return self._client
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import loguru
|
|
4
|
+
import orjson
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from core_framework.core.context import client_ip, request_id, user_id, user_ip
|
|
8
|
+
from core_framework.core.settings import Settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InterceptHandler(logging.Handler):
|
|
12
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
13
|
+
try:
|
|
14
|
+
level: str | int = logger.level(record.levelname).name
|
|
15
|
+
except ValueError:
|
|
16
|
+
level = record.levelno
|
|
17
|
+
|
|
18
|
+
frame = logging.currentframe()
|
|
19
|
+
depth = 2
|
|
20
|
+
while frame and frame.f_code.co_filename == logging.__file__:
|
|
21
|
+
frame = frame.f_back
|
|
22
|
+
depth += 1
|
|
23
|
+
|
|
24
|
+
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _configure_uvicorn_logging() -> None:
|
|
28
|
+
logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
|
|
29
|
+
|
|
30
|
+
for name in logging.root.manager.loggerDict.keys():
|
|
31
|
+
if name.startswith("uvicorn"):
|
|
32
|
+
logging.getLogger(name).handlers = [InterceptHandler()]
|
|
33
|
+
logging.getLogger(name).propagate = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _custom_serializer(record: loguru.Record) -> str:
|
|
37
|
+
subset = {
|
|
38
|
+
"time": record["time"].isoformat(),
|
|
39
|
+
"level": record["level"].name,
|
|
40
|
+
"message": record["message"],
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if record["extra"].get("context"):
|
|
44
|
+
subset.update(record["extra"]["context"])
|
|
45
|
+
|
|
46
|
+
return orjson.dumps(subset).decode()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _add_context_to_record(record: loguru.Record) -> None:
|
|
50
|
+
if "context" not in record["extra"]:
|
|
51
|
+
record["extra"]["context"] = {}
|
|
52
|
+
|
|
53
|
+
if (user_id_value := user_id.get()) is not None:
|
|
54
|
+
record["extra"]["context"]["user_id"] = user_id_value
|
|
55
|
+
if (user_ip_value := user_ip.get()) is not None:
|
|
56
|
+
record["extra"]["context"]["user_ip"] = user_ip_value
|
|
57
|
+
if (client_ip_value := client_ip.get()) is not None:
|
|
58
|
+
record["extra"]["context"]["client_ip"] = client_ip_value
|
|
59
|
+
if (request_id_value := request_id.get()) is not None:
|
|
60
|
+
record["extra"]["context"]["request_id"] = request_id_value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _patch_serializer(record: loguru.Record) -> None:
|
|
64
|
+
_add_context_to_record(record)
|
|
65
|
+
record["extra"]["json_output"] = _custom_serializer(record)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def setup_logging(settings: Settings) -> None:
|
|
69
|
+
if settings.app.environment == "local":
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
if settings.observability.enabled:
|
|
73
|
+
patched_logger = logger.patch(_add_context_to_record)
|
|
74
|
+
|
|
75
|
+
loguru.logger = patched_logger
|
|
76
|
+
globals()["logger"] = patched_logger
|
|
77
|
+
|
|
78
|
+
_configure_uvicorn_logging()
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
logger.remove()
|
|
82
|
+
patched_logger = logger.patch(_patch_serializer)
|
|
83
|
+
|
|
84
|
+
loguru.logger = patched_logger
|
|
85
|
+
globals()["logger"] = patched_logger
|
|
86
|
+
|
|
87
|
+
patched_logger.add(
|
|
88
|
+
settings.logging.sink,
|
|
89
|
+
format="{extra[json_output]}",
|
|
90
|
+
level=settings.logging.level,
|
|
91
|
+
rotation="10 MB",
|
|
92
|
+
retention="7 days",
|
|
93
|
+
compression="gz",
|
|
94
|
+
enqueue=True,
|
|
95
|
+
backtrace=True,
|
|
96
|
+
diagnose=False,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
_configure_uvicorn_logging()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from brotli_asgi import BrotliMiddleware
|
|
2
|
+
from fastapi import FastAPI, status
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
7
|
+
from ulid import ULID
|
|
8
|
+
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
|
9
|
+
|
|
10
|
+
from core_framework.core.context import REQUEST_CONTEXTVARS, client_ip, request_id
|
|
11
|
+
from core_framework.core.settings import Settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ContextMiddleware:
|
|
15
|
+
def __init__(self, app: ASGIApp) -> None:
|
|
16
|
+
self.app = app
|
|
17
|
+
|
|
18
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
19
|
+
if scope["type"] != "http":
|
|
20
|
+
return await self.app(scope, receive, send)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
if scope.get("path", "") not in {"/health", "/readiness"}:
|
|
24
|
+
client = scope["client"]
|
|
25
|
+
if client is None:
|
|
26
|
+
response = JSONResponse(
|
|
27
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
28
|
+
content={"detail": "Client IP could not be determined"},
|
|
29
|
+
)
|
|
30
|
+
await response(scope, receive, send)
|
|
31
|
+
return
|
|
32
|
+
client_ip.set(client[0])
|
|
33
|
+
request_id.set(str(ULID()))
|
|
34
|
+
await self.app(scope, receive, send)
|
|
35
|
+
finally:
|
|
36
|
+
for contextvar in REQUEST_CONTEXTVARS:
|
|
37
|
+
contextvar.set(None)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def setup_middleware(app: FastAPI, settings: Settings) -> None:
|
|
41
|
+
app.add_middleware(ContextMiddleware) # ty: ignore[invalid-argument-type]
|
|
42
|
+
|
|
43
|
+
app.add_middleware(
|
|
44
|
+
BrotliMiddleware, # ty: ignore[invalid-argument-type]
|
|
45
|
+
minimum_size=102_400, # Decrease this if no longer using cloudflare
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
app.add_middleware(
|
|
49
|
+
CORSMiddleware, # ty: ignore[invalid-argument-type]
|
|
50
|
+
allow_credentials=settings.cors.allow_credentials,
|
|
51
|
+
allow_origins=settings.cors.allowed_origins,
|
|
52
|
+
allow_methods=settings.cors.allowed_methods,
|
|
53
|
+
allow_headers=settings.cors.allowed_headers,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
app.add_middleware(
|
|
57
|
+
TrustedHostMiddleware, # ty: ignore[invalid-argument-type]
|
|
58
|
+
allowed_hosts=settings.app.allowed_hosts,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
app.add_middleware(
|
|
62
|
+
ProxyHeadersMiddleware, # ty: ignore[invalid-argument-type]
|
|
63
|
+
trusted_hosts=["*"],
|
|
64
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import logfire
|
|
2
|
+
import loguru
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
|
|
5
|
+
from core_framework.core.settings import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_observability(app: FastAPI, settings: Settings) -> None:
|
|
9
|
+
if settings.app.environment == "local":
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
if not settings.observability.enabled:
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
logfire.configure(
|
|
16
|
+
service_name="core_framework-api",
|
|
17
|
+
token=settings.observability.logfire_token,
|
|
18
|
+
min_level=settings.logging.level,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
loguru.logger.configure(handlers=[logfire.loguru_handler()])
|
|
22
|
+
|
|
23
|
+
logfire.instrument_httpx()
|
|
24
|
+
|
|
25
|
+
logfire.instrument_asyncpg()
|
|
26
|
+
|
|
27
|
+
logfire.instrument_redis()
|
|
28
|
+
|
|
29
|
+
logfire.instrument_fastapi(
|
|
30
|
+
app=app,
|
|
31
|
+
excluded_urls=[
|
|
32
|
+
"/health",
|
|
33
|
+
"/readiness",
|
|
34
|
+
],
|
|
35
|
+
capture_headers=False,
|
|
36
|
+
)
|