franchise-cloud-utils 0.1.0__tar.gz
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.
- franchise_cloud_utils-0.1.0/PKG-INFO +22 -0
- franchise_cloud_utils-0.1.0/README.md +10 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.egg-info/PKG-INFO +22 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.egg-info/SOURCES.txt +8 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.egg-info/dependency_links.txt +1 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.egg-info/requires.txt +1 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.egg-info/top_level.txt +1 -0
- franchise_cloud_utils-0.1.0/franchise_cloud_utils.py +227 -0
- franchise_cloud_utils-0.1.0/pyproject.toml +26 -0
- franchise_cloud_utils-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: franchise-cloud-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Advanced, scalable Cloud utilities for robust Django applications.
|
|
5
|
+
Author: Franchise Team
|
|
6
|
+
Project-URL: Homepage, https://github.com/your-org/franchise-cloud-utils
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: boto3>=1.28.0
|
|
12
|
+
|
|
13
|
+
# franchise-cloud-utils
|
|
14
|
+
|
|
15
|
+
Advanced programming constructs and utilities to create secure, dynamic, configurable, robust, and scalable cloud-based server applications.
|
|
16
|
+
|
|
17
|
+
## Modules included:
|
|
18
|
+
|
|
19
|
+
1. **Validators** (`franchise_cloud_utils.validator`): Employs Python descriptors to securely ensure data constraints without relying strictly on ORM validation.
|
|
20
|
+
2. **RBAC** (`franchise_cloud_utils.rbac`): Uses metaprogramming for dynamic, robust role-based access control registries.
|
|
21
|
+
3. **Events** (`franchise_cloud_utils.events`): Implements an observer pattern for decoupled application logic.
|
|
22
|
+
4. **Cloud** (`franchise_cloud_utils.cloud`): Resilient AWS utility abstractions, featuring exponential backoff and singletons.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# franchise-cloud-utils
|
|
2
|
+
|
|
3
|
+
Advanced programming constructs and utilities to create secure, dynamic, configurable, robust, and scalable cloud-based server applications.
|
|
4
|
+
|
|
5
|
+
## Modules included:
|
|
6
|
+
|
|
7
|
+
1. **Validators** (`franchise_cloud_utils.validator`): Employs Python descriptors to securely ensure data constraints without relying strictly on ORM validation.
|
|
8
|
+
2. **RBAC** (`franchise_cloud_utils.rbac`): Uses metaprogramming for dynamic, robust role-based access control registries.
|
|
9
|
+
3. **Events** (`franchise_cloud_utils.events`): Implements an observer pattern for decoupled application logic.
|
|
10
|
+
4. **Cloud** (`franchise_cloud_utils.cloud`): Resilient AWS utility abstractions, featuring exponential backoff and singletons.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: franchise-cloud-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Advanced, scalable Cloud utilities for robust Django applications.
|
|
5
|
+
Author: Franchise Team
|
|
6
|
+
Project-URL: Homepage, https://github.com/your-org/franchise-cloud-utils
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: boto3>=1.28.0
|
|
12
|
+
|
|
13
|
+
# franchise-cloud-utils
|
|
14
|
+
|
|
15
|
+
Advanced programming constructs and utilities to create secure, dynamic, configurable, robust, and scalable cloud-based server applications.
|
|
16
|
+
|
|
17
|
+
## Modules included:
|
|
18
|
+
|
|
19
|
+
1. **Validators** (`franchise_cloud_utils.validator`): Employs Python descriptors to securely ensure data constraints without relying strictly on ORM validation.
|
|
20
|
+
2. **RBAC** (`franchise_cloud_utils.rbac`): Uses metaprogramming for dynamic, robust role-based access control registries.
|
|
21
|
+
3. **Events** (`franchise_cloud_utils.events`): Implements an observer pattern for decoupled application logic.
|
|
22
|
+
4. **Cloud** (`franchise_cloud_utils.cloud`): Resilient AWS utility abstractions, featuring exponential backoff and singletons.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
franchise_cloud_utils.py
|
|
3
|
+
pyproject.toml
|
|
4
|
+
franchise_cloud_utils.egg-info/PKG-INFO
|
|
5
|
+
franchise_cloud_utils.egg-info/SOURCES.txt
|
|
6
|
+
franchise_cloud_utils.egg-info/dependency_links.txt
|
|
7
|
+
franchise_cloud_utils.egg-info/requires.txt
|
|
8
|
+
franchise_cloud_utils.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
boto3>=1.28.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
franchise_cloud_utils
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Franchise Cloud Utilities (Single File Module)
|
|
3
|
+
Advanced constructs for secure, dynamic, scalable cloud-based applications.
|
|
4
|
+
|
|
5
|
+
Version 0.1.0
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
import logging
|
|
10
|
+
import threading
|
|
11
|
+
from functools import wraps
|
|
12
|
+
from typing import Callable, Dict, List, Any
|
|
13
|
+
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
# Data Validation Descriptors
|
|
16
|
+
# ==============================================================================
|
|
17
|
+
|
|
18
|
+
class BaseField:
|
|
19
|
+
def __init__(self, name=None):
|
|
20
|
+
self.name = name
|
|
21
|
+
|
|
22
|
+
def __set_name__(self, owner, name):
|
|
23
|
+
self.name = name
|
|
24
|
+
|
|
25
|
+
def __get__(self, instance, owner):
|
|
26
|
+
if instance is None:
|
|
27
|
+
return self
|
|
28
|
+
return instance.__dict__.get(self.name)
|
|
29
|
+
|
|
30
|
+
class StringField(BaseField):
|
|
31
|
+
def __init__(self, min_length=0, max_length=None, pattern=None, **kwargs):
|
|
32
|
+
super().__init__(**kwargs)
|
|
33
|
+
self.min_length = min_length
|
|
34
|
+
self.max_length = max_length
|
|
35
|
+
self.pattern = re.compile(pattern) if pattern else None
|
|
36
|
+
|
|
37
|
+
def __set__(self, instance, value):
|
|
38
|
+
if not isinstance(value, str):
|
|
39
|
+
raise TypeError(f"'{self.name}' must be a string, got {type(value).__name__}")
|
|
40
|
+
if len(value) < self.min_length:
|
|
41
|
+
raise ValueError(f"'{self.name}' must be at least {self.min_length} characters")
|
|
42
|
+
if self.max_length and len(value) > self.max_length:
|
|
43
|
+
raise ValueError(f"'{self.name}' cannot exceed {self.max_length} characters")
|
|
44
|
+
if self.pattern and not self.pattern.match(value):
|
|
45
|
+
raise ValueError(f"'{self.name}' does not match required format")
|
|
46
|
+
|
|
47
|
+
instance.__dict__[self.name] = value
|
|
48
|
+
|
|
49
|
+
class IntegerField(BaseField):
|
|
50
|
+
def __init__(self, min_value=None, max_value=None, **kwargs):
|
|
51
|
+
super().__init__(**kwargs)
|
|
52
|
+
self.min_value = min_value
|
|
53
|
+
self.max_value = max_value
|
|
54
|
+
|
|
55
|
+
def __set__(self, instance, value):
|
|
56
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
57
|
+
raise TypeError(f"'{self.name}' must be an integer")
|
|
58
|
+
if self.min_value is not None and value < self.min_value:
|
|
59
|
+
raise ValueError(f"'{self.name}' must be >= {self.min_value}")
|
|
60
|
+
if self.max_value is not None and value > self.max_value:
|
|
61
|
+
raise ValueError(f"'{self.name}' must be <= {self.max_value}")
|
|
62
|
+
|
|
63
|
+
instance.__dict__[self.name] = value
|
|
64
|
+
|
|
65
|
+
class SchemaValidator:
|
|
66
|
+
def __init__(self, **kwargs):
|
|
67
|
+
for key, value in kwargs.items():
|
|
68
|
+
if hasattr(self.__class__, key):
|
|
69
|
+
setattr(self, key, value)
|
|
70
|
+
else:
|
|
71
|
+
raise AttributeError(f"'{self.__class__.__name__}' does not have field '{key}'")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ==============================================================================
|
|
75
|
+
# Role-Based Access Control (RBAC)
|
|
76
|
+
# ==============================================================================
|
|
77
|
+
|
|
78
|
+
_ROLE_REGISTRY = {}
|
|
79
|
+
|
|
80
|
+
class RoleRegistryMeta(type):
|
|
81
|
+
def __new__(cls, name, bases, attrs):
|
|
82
|
+
new_class = super().__new__(cls, name, bases, attrs)
|
|
83
|
+
if name != 'BaseRole':
|
|
84
|
+
_ROLE_REGISTRY[name] = new_class
|
|
85
|
+
return new_class
|
|
86
|
+
|
|
87
|
+
class BaseRole(metaclass=RoleRegistryMeta):
|
|
88
|
+
permissions = []
|
|
89
|
+
@classmethod
|
|
90
|
+
def can_access(cls, permission: str) -> bool:
|
|
91
|
+
return permission in cls.permissions
|
|
92
|
+
|
|
93
|
+
_tls = threading.local()
|
|
94
|
+
|
|
95
|
+
class sudo:
|
|
96
|
+
def __enter__(self):
|
|
97
|
+
self.original = getattr(_tls, 'is_sudo', False)
|
|
98
|
+
_tls.is_sudo = True
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
102
|
+
_tls.is_sudo = self.original
|
|
103
|
+
|
|
104
|
+
def require_roles(*allowed_roles):
|
|
105
|
+
def decorator(view_func):
|
|
106
|
+
@wraps(view_func)
|
|
107
|
+
def _wrapped_view(request, *args, **kwargs):
|
|
108
|
+
if getattr(_tls, 'is_sudo', False):
|
|
109
|
+
return view_func(request, *args, **kwargs)
|
|
110
|
+
|
|
111
|
+
user_role = getattr(request.user, 'role', None)
|
|
112
|
+
if user_role not in allowed_roles:
|
|
113
|
+
from django.core.exceptions import PermissionDenied
|
|
114
|
+
raise PermissionDenied("User does not have the required cloud privileges.")
|
|
115
|
+
|
|
116
|
+
return view_func(request, *args, **kwargs)
|
|
117
|
+
return _wrapped_view
|
|
118
|
+
return decorator
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ==============================================================================
|
|
122
|
+
# Event Observer Bus
|
|
123
|
+
# ==============================================================================
|
|
124
|
+
|
|
125
|
+
class EventSubscriber:
|
|
126
|
+
def __init__(self, callback: Callable, is_async: bool = False):
|
|
127
|
+
self.callback = callback
|
|
128
|
+
self.is_async = is_async
|
|
129
|
+
|
|
130
|
+
class EventDispatcher:
|
|
131
|
+
_instance = None
|
|
132
|
+
_lock = threading.Lock()
|
|
133
|
+
|
|
134
|
+
def __new__(cls):
|
|
135
|
+
with cls._lock:
|
|
136
|
+
if cls._instance is None:
|
|
137
|
+
cls._instance = super(EventDispatcher, cls).__new__(cls)
|
|
138
|
+
cls._instance.subscribers: Dict[str, List[EventSubscriber]] = {}
|
|
139
|
+
return cls._instance
|
|
140
|
+
|
|
141
|
+
def subscribe(self, event_type: str, callback: Callable, is_async: bool = False):
|
|
142
|
+
if event_type not in self.subscribers:
|
|
143
|
+
self.subscribers[event_type] = []
|
|
144
|
+
subscriber = EventSubscriber(callback, is_async=is_async)
|
|
145
|
+
self.subscribers[event_type].append(subscriber)
|
|
146
|
+
|
|
147
|
+
def dispatch(self, event_type: str, *args, **kwargs):
|
|
148
|
+
if event_type not in self.subscribers:
|
|
149
|
+
return
|
|
150
|
+
for subscriber in self.subscribers[event_type]:
|
|
151
|
+
if subscriber.is_async:
|
|
152
|
+
thread = threading.Thread(
|
|
153
|
+
target=subscriber.callback,
|
|
154
|
+
args=args,
|
|
155
|
+
kwargs=kwargs,
|
|
156
|
+
daemon=True
|
|
157
|
+
)
|
|
158
|
+
thread.start()
|
|
159
|
+
else:
|
|
160
|
+
subscriber.callback(*args, **kwargs)
|
|
161
|
+
|
|
162
|
+
bus = EventDispatcher()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ==============================================================================
|
|
166
|
+
# Cloud Resiliency
|
|
167
|
+
# ==============================================================================
|
|
168
|
+
|
|
169
|
+
logger = logging.getLogger(__name__)
|
|
170
|
+
|
|
171
|
+
def retry_with_backoff(retries: int = 3, initial_backoff: float = 1.0, max_backoff: float = 60.0):
|
|
172
|
+
def decorator(func):
|
|
173
|
+
@wraps(func)
|
|
174
|
+
def wrapper(*args, **kwargs):
|
|
175
|
+
backoff = initial_backoff
|
|
176
|
+
for attempt in range(retries):
|
|
177
|
+
try:
|
|
178
|
+
return func(*args, **kwargs)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
if attempt == retries - 1:
|
|
181
|
+
logger.error(f"Function {func.__name__} failed after {retries} attempts.")
|
|
182
|
+
raise e
|
|
183
|
+
logger.warning(
|
|
184
|
+
f"Attempt {attempt + 1} failed for {func.__name__}: {str(e)}. "
|
|
185
|
+
f"Retrying in {backoff} seconds..."
|
|
186
|
+
)
|
|
187
|
+
time.sleep(backoff)
|
|
188
|
+
backoff = min(backoff * 2, max_backoff)
|
|
189
|
+
return wrapper
|
|
190
|
+
return decorator
|
|
191
|
+
|
|
192
|
+
class AWSBorg:
|
|
193
|
+
_shared_state: Dict[str, Any] = {}
|
|
194
|
+
|
|
195
|
+
def __init__(self):
|
|
196
|
+
self.__dict__ = self._shared_state
|
|
197
|
+
if not hasattr(self, 'clients'):
|
|
198
|
+
self.clients: Dict[str, Any] = {}
|
|
199
|
+
|
|
200
|
+
def get_client(self, service_name: str, region_name: str = 'us-east-1'):
|
|
201
|
+
import boto3
|
|
202
|
+
key = f"{service_name}_{region_name}"
|
|
203
|
+
if key not in self.clients:
|
|
204
|
+
self.clients[key] = boto3.client(service_name, region_name=region_name)
|
|
205
|
+
return self.clients[key]
|
|
206
|
+
|
|
207
|
+
class SecretManagerCache:
|
|
208
|
+
def __init__(self, ttl_seconds: int = 300):
|
|
209
|
+
self.ttl = ttl_seconds
|
|
210
|
+
self._cache = {}
|
|
211
|
+
self._timestamps = {}
|
|
212
|
+
self._borg = AWSBorg()
|
|
213
|
+
|
|
214
|
+
@retry_with_backoff(retries=3)
|
|
215
|
+
def get_secret(self, secret_id: str) -> str:
|
|
216
|
+
now = time.time()
|
|
217
|
+
if secret_id in self._cache:
|
|
218
|
+
if now - self._timestamps.get(secret_id, 0) < self.ttl:
|
|
219
|
+
return self._cache[secret_id]
|
|
220
|
+
|
|
221
|
+
client = self._borg.get_client('secretsmanager')
|
|
222
|
+
response = client.get_secret_value(SecretId=secret_id)
|
|
223
|
+
secret_string = response.get('SecretString')
|
|
224
|
+
|
|
225
|
+
self._cache[secret_id] = secret_string
|
|
226
|
+
self._timestamps[secret_id] = now
|
|
227
|
+
return secret_string
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "franchise-cloud-utils"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Franchise Team" },
|
|
10
|
+
]
|
|
11
|
+
description = "Advanced, scalable Cloud utilities for robust Django applications."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"boto3>=1.28.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
"Homepage" = "https://github.com/your-org/franchise-cloud-utils"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
py-modules = ["franchise_cloud_utils"]
|