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.

Files changed (194) hide show
  1. geek_cafe_saas_sdk/__init__.py +9 -0
  2. geek_cafe_saas_sdk/core/__init__.py +11 -0
  3. geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
  4. geek_cafe_saas_sdk/core/error_codes.py +132 -0
  5. geek_cafe_saas_sdk/core/service_errors.py +19 -0
  6. geek_cafe_saas_sdk/core/service_result.py +121 -0
  7. geek_cafe_saas_sdk/decorators/__init__.py +64 -0
  8. geek_cafe_saas_sdk/decorators/auth.py +373 -0
  9. geek_cafe_saas_sdk/decorators/core.py +358 -0
  10. geek_cafe_saas_sdk/domains/__init__.py +0 -0
  11. geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
  12. geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
  13. geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
  14. geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
  15. geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
  16. geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
  17. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
  18. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
  19. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
  20. geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
  21. geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
  22. geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
  23. geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
  24. geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
  25. geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
  26. geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
  27. geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
  28. geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
  29. geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
  30. geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
  31. geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
  32. geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
  33. geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
  34. geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
  35. geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
  36. geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
  37. geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
  38. geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
  39. geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
  40. geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
  41. geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
  42. geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
  43. geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
  44. geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
  45. geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
  46. geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
  47. geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
  48. geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
  49. geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
  50. geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
  51. geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
  52. geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
  53. geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
  54. geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
  55. geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
  56. geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
  57. geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
  58. geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
  59. geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
  60. geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
  61. geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
  62. geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
  63. geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
  64. geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
  65. geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
  66. geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
  67. geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
  68. geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
  69. geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
  70. geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
  71. geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
  72. geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
  73. geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
  74. geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
  75. geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
  76. geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
  77. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
  78. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
  79. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
  80. geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
  81. geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
  82. geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
  83. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
  84. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
  85. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
  86. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
  87. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
  88. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
  89. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
  90. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
  91. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
  92. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
  93. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
  94. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
  95. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
  96. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
  97. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
  98. geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
  99. geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
  100. geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
  101. geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
  102. geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
  103. geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
  104. geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
  105. geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
  106. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
  107. geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
  108. geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
  109. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
  110. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
  111. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
  112. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
  113. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
  114. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
  115. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
  116. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
  117. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
  118. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
  119. geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
  120. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
  121. geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
  122. geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
  123. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
  124. geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
  125. geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
  126. geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
  127. geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
  128. geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
  129. geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
  130. geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
  131. geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
  132. geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
  133. geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
  134. geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
  135. geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
  136. geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
  137. geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
  138. geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
  139. geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
  140. geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
  141. geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
  142. geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
  143. geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
  144. geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
  145. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
  146. geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
  147. geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
  148. geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
  149. geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
  150. geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
  151. geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
  152. geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
  153. geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
  154. geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
  155. geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
  156. geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
  157. geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
  158. geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
  159. geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
  160. geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
  161. geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
  162. geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
  163. geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
  164. geek_cafe_saas_sdk/middleware/__init__.py +36 -0
  165. geek_cafe_saas_sdk/middleware/auth.py +85 -0
  166. geek_cafe_saas_sdk/middleware/authorization.py +523 -0
  167. geek_cafe_saas_sdk/middleware/cors.py +63 -0
  168. geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
  169. geek_cafe_saas_sdk/middleware/validation.py +80 -0
  170. geek_cafe_saas_sdk/models/__init__.py +20 -0
  171. geek_cafe_saas_sdk/models/base_model.py +233 -0
  172. geek_cafe_saas_sdk/services/__init__.py +18 -0
  173. geek_cafe_saas_sdk/services/database_service.py +441 -0
  174. geek_cafe_saas_sdk/utilities/__init__.py +88 -0
  175. geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
  176. geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
  177. geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
  178. geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
  179. geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
  180. geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
  181. geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
  182. geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
  183. geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
  184. geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
  185. geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
  186. geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
  187. geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
  188. geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
  189. geek_cafe_saas_sdk/utilities/response.py +209 -0
  190. geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
  191. geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
  192. geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
  193. geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
  194. 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)