mdb-engine 0.1.6__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.
Files changed (75) hide show
  1. mdb_engine/README.md +144 -0
  2. mdb_engine/__init__.py +37 -0
  3. mdb_engine/auth/README.md +631 -0
  4. mdb_engine/auth/__init__.py +128 -0
  5. mdb_engine/auth/casbin_factory.py +199 -0
  6. mdb_engine/auth/casbin_models.py +46 -0
  7. mdb_engine/auth/config_defaults.py +71 -0
  8. mdb_engine/auth/config_helpers.py +213 -0
  9. mdb_engine/auth/cookie_utils.py +158 -0
  10. mdb_engine/auth/decorators.py +350 -0
  11. mdb_engine/auth/dependencies.py +747 -0
  12. mdb_engine/auth/helpers.py +64 -0
  13. mdb_engine/auth/integration.py +578 -0
  14. mdb_engine/auth/jwt.py +225 -0
  15. mdb_engine/auth/middleware.py +241 -0
  16. mdb_engine/auth/oso_factory.py +323 -0
  17. mdb_engine/auth/provider.py +570 -0
  18. mdb_engine/auth/restrictions.py +271 -0
  19. mdb_engine/auth/session_manager.py +477 -0
  20. mdb_engine/auth/token_lifecycle.py +213 -0
  21. mdb_engine/auth/token_store.py +289 -0
  22. mdb_engine/auth/users.py +1516 -0
  23. mdb_engine/auth/utils.py +614 -0
  24. mdb_engine/cli/__init__.py +13 -0
  25. mdb_engine/cli/commands/__init__.py +7 -0
  26. mdb_engine/cli/commands/generate.py +105 -0
  27. mdb_engine/cli/commands/migrate.py +83 -0
  28. mdb_engine/cli/commands/show.py +70 -0
  29. mdb_engine/cli/commands/validate.py +63 -0
  30. mdb_engine/cli/main.py +41 -0
  31. mdb_engine/cli/utils.py +92 -0
  32. mdb_engine/config.py +217 -0
  33. mdb_engine/constants.py +160 -0
  34. mdb_engine/core/README.md +542 -0
  35. mdb_engine/core/__init__.py +42 -0
  36. mdb_engine/core/app_registration.py +392 -0
  37. mdb_engine/core/connection.py +243 -0
  38. mdb_engine/core/engine.py +749 -0
  39. mdb_engine/core/index_management.py +162 -0
  40. mdb_engine/core/manifest.py +2793 -0
  41. mdb_engine/core/seeding.py +179 -0
  42. mdb_engine/core/service_initialization.py +355 -0
  43. mdb_engine/core/types.py +413 -0
  44. mdb_engine/database/README.md +522 -0
  45. mdb_engine/database/__init__.py +31 -0
  46. mdb_engine/database/abstraction.py +635 -0
  47. mdb_engine/database/connection.py +387 -0
  48. mdb_engine/database/scoped_wrapper.py +1721 -0
  49. mdb_engine/embeddings/README.md +184 -0
  50. mdb_engine/embeddings/__init__.py +62 -0
  51. mdb_engine/embeddings/dependencies.py +193 -0
  52. mdb_engine/embeddings/service.py +759 -0
  53. mdb_engine/exceptions.py +167 -0
  54. mdb_engine/indexes/README.md +651 -0
  55. mdb_engine/indexes/__init__.py +21 -0
  56. mdb_engine/indexes/helpers.py +145 -0
  57. mdb_engine/indexes/manager.py +895 -0
  58. mdb_engine/memory/README.md +451 -0
  59. mdb_engine/memory/__init__.py +30 -0
  60. mdb_engine/memory/service.py +1285 -0
  61. mdb_engine/observability/README.md +515 -0
  62. mdb_engine/observability/__init__.py +42 -0
  63. mdb_engine/observability/health.py +296 -0
  64. mdb_engine/observability/logging.py +161 -0
  65. mdb_engine/observability/metrics.py +297 -0
  66. mdb_engine/routing/README.md +462 -0
  67. mdb_engine/routing/__init__.py +73 -0
  68. mdb_engine/routing/websockets.py +813 -0
  69. mdb_engine/utils/__init__.py +7 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +213 -0
  71. mdb_engine-0.1.6.dist-info/RECORD +75 -0
  72. mdb_engine-0.1.6.dist-info/WHEEL +5 -0
  73. mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
  74. mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
  75. mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,323 @@
1
+ """
2
+ OSO Provider Factory
3
+
4
+ Provides helper functions to auto-initialize OSO Cloud authorization provider
5
+ from manifest configuration.
6
+
7
+ This module is part of MDB_ENGINE - MongoDB Engine.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import os
14
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
15
+
16
+ if TYPE_CHECKING:
17
+ from .provider import OsoAdapter
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ async def create_oso_cloud_client(
23
+ api_key: Optional[str] = None,
24
+ url: Optional[str] = None,
25
+ max_retries: int = 3,
26
+ retry_delay: float = 2.0,
27
+ ) -> Any:
28
+ """
29
+ Create an OSO Cloud client instance with retry logic for Dev Server.
30
+
31
+ Args:
32
+ api_key: OSO Cloud API key. If None, reads from OSO_AUTH env var.
33
+ url: OSO Cloud URL. If None, reads from OSO_URL env var.
34
+ Defaults to production cloud.osohq.com if not set.
35
+ max_retries: Maximum number of retry attempts (default: 3)
36
+ retry_delay: Delay between retries in seconds (default: 2.0)
37
+
38
+ Returns:
39
+ OSO Cloud client instance
40
+
41
+ Raises:
42
+ ImportError: If oso-cloud is not installed
43
+ ValueError: If API key is not provided
44
+ """
45
+ import asyncio
46
+
47
+ # Import OSO Cloud SDK - the class is named "Oso"
48
+ try:
49
+ from oso_cloud import Oso
50
+
51
+ logger.debug("✅ Imported Oso from oso_cloud")
52
+ except ImportError:
53
+ raise ImportError(
54
+ "OSO Cloud SDK not installed. Install with: pip install oso-cloud"
55
+ )
56
+
57
+ # Get API key from parameter or environment
58
+ if not api_key:
59
+ api_key = os.getenv("OSO_AUTH")
60
+
61
+ if not api_key:
62
+ raise ValueError(
63
+ "OSO Cloud API key not provided. "
64
+ "Set OSO_AUTH environment variable or provide api_key parameter."
65
+ )
66
+
67
+ # Get URL from parameter or environment
68
+ if not url:
69
+ url = os.getenv("OSO_URL")
70
+
71
+ # Initialize OSO Cloud client with explicit parameters
72
+ # The Oso() constructor requires api_key parameter
73
+ # For Dev Server, we may need to retry if it's not ready yet
74
+ last_error = None
75
+ for attempt in range(max_retries):
76
+ try:
77
+ if url:
78
+ oso_client = Oso(api_key=api_key, url=url)
79
+ else:
80
+ oso_client = Oso(api_key=api_key)
81
+
82
+ # Note: OSO client creation doesn't actually connect to the server
83
+ # The connection happens on first API call, so we'll catch errors then
84
+ logger.info(
85
+ f"✅ OSO Cloud client created successfully (URL: {url or 'default'})"
86
+ )
87
+ if url:
88
+ logger.info(f" Using OSO Dev Server at: {url}")
89
+ return oso_client
90
+
91
+ except (
92
+ ValueError,
93
+ TypeError,
94
+ AttributeError,
95
+ RuntimeError,
96
+ ConnectionError,
97
+ ) as e:
98
+ last_error = e
99
+ if attempt < max_retries - 1:
100
+ logger.warning(
101
+ f"Failed to create OSO Cloud client "
102
+ f"(attempt {attempt + 1}/{max_retries}): {e}. "
103
+ f"Retrying in {retry_delay} seconds..."
104
+ )
105
+ await asyncio.sleep(retry_delay)
106
+ else:
107
+ logger.error(
108
+ f"Failed to create OSO Cloud client after {max_retries} attempts: {e}",
109
+ exc_info=True,
110
+ )
111
+ raise
112
+
113
+ # Should never reach here, but just in case
114
+ if last_error:
115
+ raise last_error
116
+ raise RuntimeError("Failed to create OSO Cloud client")
117
+
118
+
119
+ async def setup_initial_oso_facts(
120
+ authz_provider: OsoAdapter,
121
+ initial_roles: Optional[List[Dict[str, Any]]] = None,
122
+ initial_policies: Optional[List[Dict[str, Any]]] = None,
123
+ ) -> None:
124
+ """
125
+ Set up initial roles and policies in OSO Cloud.
126
+
127
+ Args:
128
+ authz_provider: OsoAdapter instance
129
+ initial_roles: List of role assignments, e.g.:
130
+ [{"user": "alice@example.com", "role": "admin", "resource": "documents"}]
131
+ If "resource" is not specified, defaults to "documents" for backward compatibility
132
+ initial_policies: List of permission policies, e.g.:
133
+ [{"role": "admin", "resource": "documents", "action": "read"}]
134
+ """
135
+ if initial_roles:
136
+ for role_assignment in initial_roles:
137
+ try:
138
+ user = role_assignment.get("user")
139
+ role = role_assignment.get("role")
140
+ resource = role_assignment.get(
141
+ "resource", "documents"
142
+ ) # Default to "documents"
143
+
144
+ if user and role:
145
+ # For OSO Cloud, we add has_role facts with resource context
146
+ # This supports resource-based authorization
147
+ await authz_provider.add_role_for_user(user, role, resource)
148
+ logger.debug(
149
+ f"Added role '{role}' for user '{user}' on resource '{resource}'"
150
+ )
151
+ except (ValueError, TypeError, AttributeError, RuntimeError) as e:
152
+ logger.warning(
153
+ f"Failed to add initial role assignment {role_assignment}: {e}"
154
+ )
155
+
156
+ # Note: initial_policies are not used - we use has_role facts instead
157
+ # The policy derives permissions from roles, not from explicit grants_permission facts
158
+
159
+
160
+ async def initialize_oso_from_manifest(
161
+ engine, app_slug: str, auth_config: Dict[str, Any]
162
+ ) -> Optional[OsoAdapter]:
163
+ """
164
+ Initialize OSO Cloud provider from manifest configuration.
165
+
166
+ Args:
167
+ engine: MongoDBEngine instance
168
+ app_slug: App slug identifier
169
+ auth_config: Auth configuration dict from manifest (contains auth_policy)
170
+
171
+ Returns:
172
+ OsoAdapter instance if successfully created, None otherwise
173
+ """
174
+ try:
175
+ from .provider import OsoAdapter
176
+
177
+ # Support both old (auth_policy) and new (auth.policy) structures
178
+ auth = auth_config.get("auth", {})
179
+ auth_policy = auth.get("policy", {}) or auth_config.get("auth_policy", {})
180
+ provider = auth_policy.get("provider", "casbin")
181
+
182
+ # Only proceed if provider is oso
183
+ if provider != "oso":
184
+ logger.debug(
185
+ f"Provider is '{provider}', not 'oso' - skipping OSO initialization"
186
+ )
187
+ return None
188
+
189
+ logger.info(f"Initializing OSO Cloud provider for app '{app_slug}'...")
190
+
191
+ # Get authorization config
192
+ authorization = auth_policy.get("authorization", {})
193
+
194
+ # Get API key from config or environment
195
+ api_key = authorization.get("api_key") or os.getenv("OSO_AUTH")
196
+ url = authorization.get("url") or os.getenv("OSO_URL")
197
+
198
+ logger.debug(
199
+ f"OSO config - API key: {'***' if api_key else 'NOT SET'}, "
200
+ f"URL: {url or 'NOT SET (using default)'}"
201
+ )
202
+
203
+ if not api_key:
204
+ logger.error(
205
+ f"❌ OSO Cloud API key not found for app '{app_slug}'. "
206
+ "Set OSO_AUTH environment variable or provide api_key in manifest."
207
+ )
208
+ return None
209
+
210
+ # Create OSO Cloud client
211
+ logger.debug("Creating OSO Cloud client...")
212
+ try:
213
+ # More retries for Dev Server which might take time to start
214
+ oso_client = await create_oso_cloud_client(
215
+ api_key=api_key,
216
+ url=url,
217
+ max_retries=5,
218
+ retry_delay=3.0, # 3 second delay between retries
219
+ )
220
+ logger.info("✅ OSO Cloud client created successfully")
221
+
222
+ # Test connection by trying a simple authorize call
223
+ # This will fail if the Dev Server isn't ready or policy isn't loaded
224
+ try:
225
+ import asyncio
226
+
227
+ from oso_cloud import Value
228
+
229
+ # Try a simple test authorization to verify connection
230
+ test_actor = Value("User", "test")
231
+ test_resource = Value("Document", "test")
232
+ # This might fail, but it tests if the server is responding
233
+ await asyncio.to_thread(
234
+ oso_client.authorize, test_actor, "read", test_resource
235
+ )
236
+ logger.debug("✅ OSO Dev Server connection test successful")
237
+ except (TimeoutError, OSError, RuntimeError) as test_error:
238
+ # Type 2: Recoverable - connection test failed, check if it's a connection error
239
+ error_str = str(test_error).lower()
240
+ if (
241
+ "connection" in error_str
242
+ or "refused" in error_str
243
+ or "timeout" in error_str
244
+ ):
245
+ logger.warning(
246
+ f"⚠️ OSO Dev Server connection test failed - "
247
+ f"server might not be ready: {test_error}"
248
+ )
249
+ logger.warning(
250
+ " This is OK if the Dev Server is still starting up. "
251
+ "Will retry on first real request."
252
+ )
253
+ else:
254
+ # Other errors (like authorization denied) are expected and OK
255
+ logger.debug(
256
+ f"OSO connection test completed "
257
+ f"(authorization denied is expected): {test_error}"
258
+ )
259
+ except (
260
+ ImportError,
261
+ AttributeError,
262
+ TypeError,
263
+ ValueError,
264
+ RuntimeError,
265
+ ConnectionError,
266
+ ) as e:
267
+ logger.error(f"❌ Failed to create OSO Cloud client: {e}", exc_info=True)
268
+ return None
269
+
270
+ # Create adapter
271
+ logger.debug("Creating OsoAdapter...")
272
+ adapter = OsoAdapter(oso_client)
273
+ logger.info("✅ OsoAdapter created successfully")
274
+
275
+ # Set up initial facts if configured
276
+ initial_roles = authorization.get("initial_roles", [])
277
+ initial_policies = authorization.get("initial_policies", [])
278
+
279
+ if initial_roles or initial_policies:
280
+ logger.info(
281
+ f"Setting up initial OSO facts: {len(initial_roles)} roles, "
282
+ f"{len(initial_policies)} policies"
283
+ )
284
+ try:
285
+ await setup_initial_oso_facts(
286
+ adapter,
287
+ initial_roles=initial_roles,
288
+ initial_policies=initial_policies,
289
+ )
290
+ logger.info("✅ Initial OSO facts set up successfully")
291
+ except (ValueError, TypeError, AttributeError, RuntimeError) as e:
292
+ logger.warning(
293
+ f"⚠️ Failed to set up initial OSO facts: {e}", exc_info=True
294
+ )
295
+ # Continue anyway - adapter is still usable
296
+
297
+ logger.info(f"✅ OSO Cloud provider initialized for app '{app_slug}'")
298
+
299
+ return adapter
300
+
301
+ except ImportError as e:
302
+ logger.error(
303
+ f"❌ OSO Cloud SDK not available for app '{app_slug}': {e}. "
304
+ "Install with: pip install oso-cloud"
305
+ )
306
+ return None
307
+ except ValueError as e:
308
+ logger.error(f"❌ OSO Cloud configuration error for app '{app_slug}': {e}")
309
+ return None
310
+ except (
311
+ ImportError,
312
+ AttributeError,
313
+ TypeError,
314
+ ValueError,
315
+ RuntimeError,
316
+ ConnectionError,
317
+ KeyError,
318
+ ) as e:
319
+ logger.error(
320
+ f"❌ Error initializing OSO Cloud provider for app '{app_slug}': {e}",
321
+ exc_info=True,
322
+ )
323
+ return None