geek-cafe-saas-sdk 0.6.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 geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Custom exceptions for the Geek Cafe SaaS Services application.
|
|
2
|
+
This module contains all custom exceptions used throughout the application.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .http_status_code import HttpStatusCodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Error(Exception):
|
|
9
|
+
"""Base class for exceptions in this module."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, status_code: int, details: str | None = None):
|
|
12
|
+
"""Initialize the base error class.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
message: The error message
|
|
16
|
+
status_code: HTTP status code
|
|
17
|
+
details: Optional additional details
|
|
18
|
+
"""
|
|
19
|
+
self.message = {
|
|
20
|
+
"status_code": status_code,
|
|
21
|
+
"message": message,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if details is not None:
|
|
25
|
+
self.message["details"] = details
|
|
26
|
+
|
|
27
|
+
super().__init__(self.message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DbFailures(Error):
|
|
31
|
+
"""Exception for database failures."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message: str = "Database operation failed"):
|
|
34
|
+
super().__init__(
|
|
35
|
+
message=message,
|
|
36
|
+
status_code=HttpStatusCodes.HTTP_422_UNEXPECTED_OUTCOME.value
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class UnknownUserException(Error):
|
|
41
|
+
"""Exception for unknown user errors."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str = "Unknown User Exception. The user account is not valid"):
|
|
44
|
+
super().__init__(
|
|
45
|
+
message=message,
|
|
46
|
+
status_code=HttpStatusCodes.HTTP_404_NOT_FOUND.value
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UserAccountPermissionException(Error):
|
|
51
|
+
"""Exception for user permission errors."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
message: str = "You are not authorized for the requested action.",
|
|
56
|
+
details: str | None = None,
|
|
57
|
+
):
|
|
58
|
+
super().__init__(
|
|
59
|
+
message=message,
|
|
60
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value,
|
|
61
|
+
details=details
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class UserAccountSubscriptionException(Error):
|
|
66
|
+
"""Exception for user subscription errors."""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
message: str = "User Subscription Exception. The user accounts subscription is not valid",
|
|
71
|
+
):
|
|
72
|
+
super().__init__(
|
|
73
|
+
message=message,
|
|
74
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SubscriptionException(Error):
|
|
79
|
+
"""Exception for organization subscription errors."""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
message: str = "Organization Subscription Exception. The organizations accounts subscription is not valid",
|
|
84
|
+
):
|
|
85
|
+
super().__init__(
|
|
86
|
+
message=message,
|
|
87
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SecurityError(Error):
|
|
92
|
+
"""Exception for security-related errors."""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
message: str = "You are not authorized to make this action.",
|
|
97
|
+
):
|
|
98
|
+
super().__init__(
|
|
99
|
+
message=message,
|
|
100
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TenancyStatusException(Error):
|
|
105
|
+
"""Exception for tenancy status errors."""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
message: str = "Tenancy Exception. The organizations accounts is not active",
|
|
110
|
+
):
|
|
111
|
+
super().__init__(
|
|
112
|
+
message=message,
|
|
113
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SubscriptionDisabledException(Error):
|
|
118
|
+
"""Exception for disabled subscription errors."""
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
message: str = "Disabled Subscription Exception. The organizations subscription has been disabled.",
|
|
123
|
+
):
|
|
124
|
+
super().__init__(
|
|
125
|
+
message=message,
|
|
126
|
+
status_code=HttpStatusCodes.HTTP_403_FORBIDDEN.value
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class UnknownParameterService(Error):
|
|
131
|
+
"""Exception for unknown parameter service errors."""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
message: str = "An unknown parameter service has been requested",
|
|
136
|
+
):
|
|
137
|
+
message = (
|
|
138
|
+
f"{message} "
|
|
139
|
+
f"Check the dose type and dose frequency configurations. "
|
|
140
|
+
f"Expected configurations: Frequency=[single|steady-state]. Type=[ev|iv-bolus|iv-infusion]."
|
|
141
|
+
)
|
|
142
|
+
super().__init__(
|
|
143
|
+
message=message,
|
|
144
|
+
status_code=HttpStatusCodes.HTTP_404_NOT_FOUND.value
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class GeneralUserException(Error):
|
|
149
|
+
"""Exception for general user errors."""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
message: str = "Unknown Error Occurred with user",
|
|
154
|
+
code: int = HttpStatusCodes.HTTP_422_UNEXPECTED_OUTCOME.value,
|
|
155
|
+
):
|
|
156
|
+
super().__init__(
|
|
157
|
+
message=message,
|
|
158
|
+
status_code=code
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class InvalidHttpMethod(Error):
|
|
163
|
+
"""Exception for invalid HTTP method errors."""
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
message: str = "Invalid Http Method",
|
|
168
|
+
code: int = HttpStatusCodes.HTTP_422_UNEXPECTED_OUTCOME.value,
|
|
169
|
+
):
|
|
170
|
+
super().__init__(
|
|
171
|
+
message=message,
|
|
172
|
+
status_code=code
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class InvalidRoutePath(Error):
|
|
177
|
+
"""Exception for invalid route path errors."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, message: str = "Invalid Route"):
|
|
180
|
+
super().__init__(
|
|
181
|
+
message=message,
|
|
182
|
+
status_code=HttpStatusCodes.HTTP_404_NOT_FOUND.value
|
|
183
|
+
)
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from datetime import UTC, datetime, timedelta, timezone
|
|
3
|
+
|
|
4
|
+
import pytz
|
|
5
|
+
from dateutil.relativedelta import relativedelta
|
|
6
|
+
|
|
7
|
+
from geek_cafe_saas_sdk.utilities.logging_utility import LoggingUtility, LogLevels
|
|
8
|
+
from geek_cafe_saas_sdk import __version__
|
|
9
|
+
|
|
10
|
+
logger = LoggingUtility.get_logger(__name__, LogLevels.INFO)
|
|
11
|
+
|
|
12
|
+
_last_timestamp = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatetimeUtility:
|
|
16
|
+
"""Date Time Utility"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def greater_than_minutes(dt: datetime | None, minutes: int) -> bool:
|
|
20
|
+
"""Check if a datetime is greater than a number of minutes"""
|
|
21
|
+
|
|
22
|
+
if dt is None:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
now = DatetimeUtility.get_utc_now()
|
|
26
|
+
delta = now - dt
|
|
27
|
+
return delta.total_seconds() > (minutes * 60)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def get_elapsed_time(start: datetime, end: datetime | None = None) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Get the elapsed time in a string format of days, hours, minutes, seconds
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
start (datetime): The start date/time
|
|
36
|
+
end (datetime | None, optional): The end time. Defaults to None.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
str: elapsed time in a string format of n days, hours, minutes, seconds, milliseconds
|
|
40
|
+
"""
|
|
41
|
+
if not isinstance(start, datetime):
|
|
42
|
+
raise ValueError("start must be a datetime")
|
|
43
|
+
|
|
44
|
+
end = end or DatetimeUtility.get_utc_now()
|
|
45
|
+
delta: timedelta = end - start
|
|
46
|
+
|
|
47
|
+
total_seconds = delta.total_seconds()
|
|
48
|
+
days = int(total_seconds // (3600 * 24))
|
|
49
|
+
total_seconds %= 3600 * 24
|
|
50
|
+
hours = int(total_seconds // 3600)
|
|
51
|
+
total_seconds %= 3600
|
|
52
|
+
minutes = int(total_seconds // 60)
|
|
53
|
+
seconds = int(total_seconds % 60)
|
|
54
|
+
milliseconds = int(delta.microseconds / 1000)
|
|
55
|
+
time_span = f"{days} days, {hours} hours, {minutes} minutes, {seconds} seconds, {milliseconds} milliseconds"
|
|
56
|
+
|
|
57
|
+
return time_span
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def get_timestamp_or_none(value: datetime | None | str) -> float | None:
|
|
61
|
+
"""Get a timestamp from a date"""
|
|
62
|
+
if value is None:
|
|
63
|
+
return None
|
|
64
|
+
if not isinstance(value, datetime):
|
|
65
|
+
value = DatetimeUtility.to_datetime_utc(value=value)
|
|
66
|
+
|
|
67
|
+
if not isinstance(value, datetime):
|
|
68
|
+
return None
|
|
69
|
+
ts = value.timestamp()
|
|
70
|
+
return ts
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_timestamp_or_empty_string(value: datetime | None | str) -> float | str:
|
|
74
|
+
"""Gets a timestamp or an empty string"""
|
|
75
|
+
result = DatetimeUtility.get_timestamp_or_none(value=value)
|
|
76
|
+
if result is None:
|
|
77
|
+
return ""
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def timestamp_to_datetime_utc(value: float | int) -> datetime:
|
|
82
|
+
"""Converts a timestamp to a datetime in UTC"""
|
|
83
|
+
try:
|
|
84
|
+
value = int(value)
|
|
85
|
+
d = datetime.fromtimestamp(value, tz=UTC)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(e)
|
|
88
|
+
return d
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def get_timestamp(value: datetime | None | str) -> float:
|
|
92
|
+
"""Get a timestampe from a date"""
|
|
93
|
+
if value is None:
|
|
94
|
+
return 0.0
|
|
95
|
+
if not isinstance(value, datetime):
|
|
96
|
+
value = DatetimeUtility.to_datetime_utc(value=value)
|
|
97
|
+
|
|
98
|
+
if not isinstance(value, datetime):
|
|
99
|
+
return 0.0
|
|
100
|
+
ts = value.timestamp()
|
|
101
|
+
return ts
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def get_start_time() -> datetime:
|
|
105
|
+
"""Gets the current datetime from get_utc_now()"""
|
|
106
|
+
return DatetimeUtility.get_utc_now()
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get_utc_now() -> datetime:
|
|
110
|
+
"""Gets Now in the proper UTC datetime format"""
|
|
111
|
+
# datetime.utcnow()
|
|
112
|
+
# below is the prefered over datetime.utcnow()
|
|
113
|
+
return datetime.now(timezone.utc)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def string_to_date(string_date: str | datetime | None) -> datetime | None:
|
|
117
|
+
"""
|
|
118
|
+
Description: takes a string value and returns it as a datetime.
|
|
119
|
+
If the value is already a datetime type, it will return it as is, otherwise
|
|
120
|
+
the returned value is None
|
|
121
|
+
string_date: str must be in format of %Y-%m-%dT%H:%M:%S.%f
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
if not string_date or str(string_date) == "None":
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
if isinstance(string_date, datetime):
|
|
128
|
+
return string_date
|
|
129
|
+
|
|
130
|
+
if "Z" in str(string_date):
|
|
131
|
+
string_date = str(string_date).replace("Z", "+00:00")
|
|
132
|
+
string_date = str(string_date)
|
|
133
|
+
string_date = string_date.replace(" ", "T")
|
|
134
|
+
string_date = string_date.replace("Z", "")
|
|
135
|
+
string_date = string_date.replace("+00:00", "")
|
|
136
|
+
|
|
137
|
+
date_formats = [
|
|
138
|
+
"%Y-%m-%dT%H:%M:%S.%f",
|
|
139
|
+
"%Y-%m-%dT%H:%M:%S",
|
|
140
|
+
"%Y-%m-%d",
|
|
141
|
+
"%m-%d-%Y",
|
|
142
|
+
"%m-%d-%y",
|
|
143
|
+
"%Y/%m/%d",
|
|
144
|
+
"%m/%d/%Y",
|
|
145
|
+
"%m/%d/%y",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
result: datetime | None = None
|
|
149
|
+
try:
|
|
150
|
+
if isinstance(string_date, str):
|
|
151
|
+
for date_format in date_formats:
|
|
152
|
+
try:
|
|
153
|
+
result = datetime.strptime(string_date, date_format)
|
|
154
|
+
break
|
|
155
|
+
except ValueError:
|
|
156
|
+
pass
|
|
157
|
+
# if nothing the we need to raise an error
|
|
158
|
+
if result is None:
|
|
159
|
+
raise ValueError(f"Unable to parse date: {string_date}")
|
|
160
|
+
|
|
161
|
+
elif isinstance(string_date, datetime):
|
|
162
|
+
result = string_date
|
|
163
|
+
else:
|
|
164
|
+
logger.warning(
|
|
165
|
+
{
|
|
166
|
+
"metric_filter": "string_to_date_warning",
|
|
167
|
+
"datetime": string_date,
|
|
168
|
+
"action": "returning none",
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
except Exception as e: # noqa: E722, pylint: disable=W0718
|
|
172
|
+
msg = {
|
|
173
|
+
"metric_filter": "string_to_date_error",
|
|
174
|
+
"datetime": string_date,
|
|
175
|
+
"error": str(e),
|
|
176
|
+
"action": "returning none",
|
|
177
|
+
"type": type(string_date).__name__,
|
|
178
|
+
"accepted_formats": date_formats,
|
|
179
|
+
"library_version": __version__,
|
|
180
|
+
}
|
|
181
|
+
logger.error(msg=msg)
|
|
182
|
+
|
|
183
|
+
raise RuntimeError(msg) from e
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def to_datetime(
|
|
189
|
+
value: str | datetime | None, default: datetime | None = None, tzinfo=UTC
|
|
190
|
+
) -> datetime | None:
|
|
191
|
+
"""
|
|
192
|
+
Description: takes a value and attempts to turn it into a datetime object
|
|
193
|
+
Returns: datetime or None
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
result = DatetimeUtility.string_to_date(value)
|
|
197
|
+
|
|
198
|
+
if result is None and default is not None:
|
|
199
|
+
if not isinstance(default, datetime):
|
|
200
|
+
default = DatetimeUtility.string_to_date(value)
|
|
201
|
+
result = default
|
|
202
|
+
|
|
203
|
+
if result and isinstance(result, datetime):
|
|
204
|
+
result = result.replace(tzinfo=tzinfo)
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def to_datetime_utc(
|
|
210
|
+
value: str | datetime | None, default: datetime | None = None
|
|
211
|
+
) -> datetime | None:
|
|
212
|
+
"""
|
|
213
|
+
Description: takes a value and attempts to turn it into a datetime object
|
|
214
|
+
Returns: datetime or None
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
result = DatetimeUtility.to_datetime(value, default, tzinfo=UTC)
|
|
218
|
+
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def to_date_string(value, default: datetime | None | str = None) -> str | None:
|
|
223
|
+
"""
|
|
224
|
+
Description: takes a value and attempts to turn it into a datetime object
|
|
225
|
+
Returns: datetime or None
|
|
226
|
+
"""
|
|
227
|
+
if value is None:
|
|
228
|
+
return None
|
|
229
|
+
value = DatetimeUtility.to_datetime(value=value)
|
|
230
|
+
result = DatetimeUtility.to_string(value, date_format="%Y-%m-%d")
|
|
231
|
+
if result is None and default is not None:
|
|
232
|
+
result = default
|
|
233
|
+
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def to_time_string(value, default: datetime | None = None) -> str | None:
|
|
238
|
+
"""
|
|
239
|
+
Description: takes a value and attempts to turn it into a datetime object
|
|
240
|
+
Returns: datetime or None
|
|
241
|
+
"""
|
|
242
|
+
if value is None:
|
|
243
|
+
return None
|
|
244
|
+
value = DatetimeUtility.to_datetime(value=value)
|
|
245
|
+
result = f"{DatetimeUtility.to_string(value, date_format='%H:%M:%S')}+00:00"
|
|
246
|
+
if result is None and default is not None:
|
|
247
|
+
result = default
|
|
248
|
+
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def to_string(value: datetime, date_format: str = "%Y-%m-%d-%H-%M-%S-%f") -> str:
|
|
253
|
+
"""
|
|
254
|
+
Description: takes a string value and returns it as a datetime.
|
|
255
|
+
If the value is already a datetime type, it will return it as is, otherwise
|
|
256
|
+
the returned value is None
|
|
257
|
+
"""
|
|
258
|
+
# TODO: determine the format
|
|
259
|
+
if not value:
|
|
260
|
+
raise ValueError("Unable to parse date - value is None.")
|
|
261
|
+
|
|
262
|
+
result = value.strftime(date_format)
|
|
263
|
+
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def datetime_from_uuid1(uuid1: uuid.UUID) -> datetime:
|
|
268
|
+
"""
|
|
269
|
+
Converts a uuid1 to a datetime
|
|
270
|
+
"""
|
|
271
|
+
ns = 0x01B21DD213814000
|
|
272
|
+
timestamp = datetime.fromtimestamp(
|
|
273
|
+
(uuid1.time - ns) * 100 / 1e9, tz=timezone.utc
|
|
274
|
+
)
|
|
275
|
+
return timestamp
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def fromtimestamp(value: float, default=None) -> datetime:
|
|
279
|
+
result = default
|
|
280
|
+
try:
|
|
281
|
+
if "-" in str(value):
|
|
282
|
+
value = float(str(value).replace("-", "."))
|
|
283
|
+
result = datetime.fromtimestamp(float(value))
|
|
284
|
+
except Exception as e: # pylint: disable=w0718
|
|
285
|
+
logger.error(str(e))
|
|
286
|
+
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def uuid1_utc(node=0, clock_seq=0, timestamp=None):
|
|
291
|
+
global _last_timestamp # pylint: disable=w0603
|
|
292
|
+
|
|
293
|
+
if not timestamp:
|
|
294
|
+
timestamp = float(DatetimeUtility.get_utc_now().timestamp())
|
|
295
|
+
if isinstance(timestamp, datetime):
|
|
296
|
+
timestamp = timestamp.timestamp()
|
|
297
|
+
|
|
298
|
+
nanoseconds = int(timestamp * 1e9)
|
|
299
|
+
# import time
|
|
300
|
+
# t = time.time_ns()
|
|
301
|
+
# ns = int(t * 1e9)
|
|
302
|
+
|
|
303
|
+
# 0x01b21dd213814000 is the number of 100-ns intervals between the
|
|
304
|
+
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
|
|
305
|
+
timestamp = nanoseconds // 100 + 0x01B21DD213814000
|
|
306
|
+
if _last_timestamp is not None and timestamp <= _last_timestamp:
|
|
307
|
+
timestamp = _last_timestamp + 1
|
|
308
|
+
_last_timestamp = timestamp
|
|
309
|
+
if clock_seq is None:
|
|
310
|
+
import random
|
|
311
|
+
|
|
312
|
+
clock_seq = random.getrandbits(14) # instead of stable storage
|
|
313
|
+
time_low = timestamp & 0xFFFFFFFF
|
|
314
|
+
time_mid = (timestamp >> 32) & 0xFFFF
|
|
315
|
+
time_hi_version = (timestamp >> 48) & 0x0FFF
|
|
316
|
+
clock_seq_low = clock_seq & 0xFF
|
|
317
|
+
clock_seq_hi_variant = (clock_seq >> 8) & 0x3F
|
|
318
|
+
if node is None:
|
|
319
|
+
node = uuid.getnode()
|
|
320
|
+
return uuid.UUID(
|
|
321
|
+
fields=(
|
|
322
|
+
time_low,
|
|
323
|
+
time_mid,
|
|
324
|
+
time_hi_version,
|
|
325
|
+
clock_seq_hi_variant,
|
|
326
|
+
clock_seq_low,
|
|
327
|
+
node,
|
|
328
|
+
),
|
|
329
|
+
version=1,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def add_year(dt: datetime, years: int = 1) -> datetime:
|
|
334
|
+
"""Add a month to the current date
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
dt (datetime): datetime
|
|
338
|
+
years (int): the number of years
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
datetime: One Month added to the input dt
|
|
342
|
+
"""
|
|
343
|
+
new_date = dt + relativedelta(years=+years)
|
|
344
|
+
new_date = new_date + relativedelta(microseconds=-1)
|
|
345
|
+
|
|
346
|
+
return new_date
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def add_month(dt: datetime, months: int = 1) -> datetime:
|
|
350
|
+
"""Add a month to the current date
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
dt (datetime): datetime
|
|
354
|
+
months (int): the number of months
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
datetime: One Month added to the input dt
|
|
358
|
+
"""
|
|
359
|
+
new_date = dt + relativedelta(months=+months)
|
|
360
|
+
new_date = new_date + relativedelta(microseconds=-1)
|
|
361
|
+
|
|
362
|
+
return new_date
|
|
363
|
+
|
|
364
|
+
@staticmethod
|
|
365
|
+
def add_days(dt: datetime, days: int = 1) -> datetime:
|
|
366
|
+
"""Add a month to the current date
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
dt (datetime): datetime
|
|
370
|
+
months (int): the number of months
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
datetime: One Month added to the input dt
|
|
374
|
+
"""
|
|
375
|
+
new_date = dt + relativedelta(days=+days)
|
|
376
|
+
new_date = new_date + relativedelta(microseconds=-1)
|
|
377
|
+
|
|
378
|
+
return new_date
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def add_minutes(dt: datetime, minutes: int = 1) -> datetime:
|
|
382
|
+
"""Add a month to the current date
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
dt (datetime): datetime
|
|
386
|
+
months (int): the number of months
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
datetime: One Month added to the input dt
|
|
390
|
+
"""
|
|
391
|
+
new_date = dt + relativedelta(minutes=+minutes)
|
|
392
|
+
new_date = new_date + relativedelta(microseconds=-1)
|
|
393
|
+
|
|
394
|
+
return new_date
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def to_timezone(utc_datetime: datetime, timezone_name: str) -> datetime:
|
|
398
|
+
"""_summary_
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
utc_datetime (datetime): datetime in utc
|
|
402
|
+
timezone (str): 'US/Eastern', 'US/Moutain', etc
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
datetime: in the correct timezone
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
tz = pytz.timezone(timezone_name)
|
|
409
|
+
result = utc_datetime.astimezone(tz)
|
|
410
|
+
return result
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DictionaryUtility:
|
|
5
|
+
"""
|
|
6
|
+
A class to provide utility methods for working with dictionaries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def find_dict_by_name(
|
|
11
|
+
dict_list: List[dict], key_field: str, name: str
|
|
12
|
+
) -> List[dict] | dict | str:
|
|
13
|
+
"""
|
|
14
|
+
Searches for dictionaries in a list where the key 'name' matches the specified value.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
dict_list (list): A list of dictionaries to search through.
|
|
18
|
+
key_field (str): The key to search for in each dictionary.
|
|
19
|
+
name (str): The value to search for in the 'key_field' key.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
list: A list of dictionaries where the 'key_field' key matches the specified value.
|
|
23
|
+
"""
|
|
24
|
+
# List comprehension to filter dictionaries that have the 'name' key equal to the specified name
|
|
25
|
+
|
|
26
|
+
return [d for d in dict_list if d.get(key_field) == name]
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def rename_keys(
|
|
30
|
+
dictionary: Dict[str, Any] | List[Dict[str, Any]], old_key: str, new_key: str
|
|
31
|
+
) -> Dict[str, Any] | List[Dict[str, Any]]:
|
|
32
|
+
"""
|
|
33
|
+
Renames a key in a dictionary.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
dictionary (dict): The dictionary to modify.
|
|
37
|
+
old_key (str): The key to be renamed.
|
|
38
|
+
new_key (str): The new key name.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
dict: The modified dictionary with the key renamed.
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(dictionary, list):
|
|
44
|
+
results: List[Dict[str, Any]] = []
|
|
45
|
+
|
|
46
|
+
for item in dictionary:
|
|
47
|
+
if isinstance(item, dict):
|
|
48
|
+
x = DictionaryUtility.rename_keys(item, old_key, new_key)
|
|
49
|
+
if isinstance(x, dict):
|
|
50
|
+
results.append(x)
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
if isinstance(item, dict):
|
|
54
|
+
results.append(item)
|
|
55
|
+
|
|
56
|
+
return results
|
|
57
|
+
if isinstance(dictionary, dict):
|
|
58
|
+
if old_key in dictionary:
|
|
59
|
+
dictionary[new_key] = dictionary.pop(old_key)
|
|
60
|
+
return dictionary
|
|
61
|
+
|
|
62
|
+
raise ValueError("Input must be a dictionary or a list of dictionaries")
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def load_json(path: str) -> dict:
|
|
66
|
+
"""
|
|
67
|
+
Loads a JSON file from the specified path.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
path (str): The path to the JSON file.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
dict: The loaded JSON data.
|
|
74
|
+
"""
|
|
75
|
+
import json
|
|
76
|
+
|
|
77
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
78
|
+
return json.load(file)
|