mdb-engine 0.4.2__py3-none-any.whl → 0.4.4__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.
- mdb_engine/__init__.py +2 -2
- mdb_engine/core/engine.py +215 -0
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/METADATA +1 -1
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/RECORD +8 -8
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/WHEEL +0 -0
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.4.2.dist-info → mdb_engine-0.4.4.dist-info}/top_level.txt +0 -0
mdb_engine/__init__.py
CHANGED
|
@@ -82,8 +82,8 @@ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
|
|
|
82
82
|
from .utils import clean_mongo_doc, clean_mongo_docs
|
|
83
83
|
|
|
84
84
|
__version__ = (
|
|
85
|
-
"0.4.
|
|
86
|
-
#
|
|
85
|
+
"0.4.3" # Feature: Automatic route import for multi-app deployments
|
|
86
|
+
# Routes from web.py/routes.py are now automatically imported when using create_multi_app()
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
__all__ = [
|
mdb_engine/core/engine.py
CHANGED
|
@@ -1729,6 +1729,199 @@ class MongoDBEngine:
|
|
|
1729
1729
|
|
|
1730
1730
|
return validation_errors
|
|
1731
1731
|
|
|
1732
|
+
def _import_app_routes(self, child_app: "FastAPI", manifest_path: Path, slug: str) -> None:
|
|
1733
|
+
"""
|
|
1734
|
+
Automatically discover and import route modules for a child app.
|
|
1735
|
+
|
|
1736
|
+
This method looks for route modules (web.py, routes.py) in the same directory
|
|
1737
|
+
as the manifest and imports them so that route decorators are executed and
|
|
1738
|
+
routes are registered on the child app.
|
|
1739
|
+
|
|
1740
|
+
Args:
|
|
1741
|
+
child_app: The FastAPI child app to register routes on
|
|
1742
|
+
manifest_path: Path to the manifest.json file
|
|
1743
|
+
slug: App slug for logging
|
|
1744
|
+
|
|
1745
|
+
The method tries multiple strategies:
|
|
1746
|
+
1. Look for 'web.py' in the manifest directory
|
|
1747
|
+
2. Look for 'routes.py' in the manifest directory
|
|
1748
|
+
3. Check manifest for explicit 'routes_module' field (future support)
|
|
1749
|
+
|
|
1750
|
+
When importing, the method ensures that route decorators in the imported module
|
|
1751
|
+
reference the child_app by temporarily injecting it into the module namespace.
|
|
1752
|
+
"""
|
|
1753
|
+
import importlib.util
|
|
1754
|
+
import sys
|
|
1755
|
+
|
|
1756
|
+
manifest_dir = manifest_path.parent
|
|
1757
|
+
|
|
1758
|
+
# Try to find route modules in order of preference
|
|
1759
|
+
route_module_paths = [
|
|
1760
|
+
manifest_dir / "web.py",
|
|
1761
|
+
manifest_dir / "routes.py",
|
|
1762
|
+
]
|
|
1763
|
+
|
|
1764
|
+
# Also check for routes_module in manifest (future support)
|
|
1765
|
+
try:
|
|
1766
|
+
import json
|
|
1767
|
+
|
|
1768
|
+
with open(manifest_path) as f:
|
|
1769
|
+
manifest_data = json.load(f)
|
|
1770
|
+
routes_module = manifest_data.get("routes_module")
|
|
1771
|
+
if routes_module:
|
|
1772
|
+
# Support both relative (to manifest dir) and absolute paths
|
|
1773
|
+
if routes_module.startswith("/"):
|
|
1774
|
+
route_module_paths.insert(0, Path(routes_module))
|
|
1775
|
+
else:
|
|
1776
|
+
route_module_paths.insert(0, manifest_dir / routes_module)
|
|
1777
|
+
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
|
1778
|
+
pass
|
|
1779
|
+
|
|
1780
|
+
imported = False
|
|
1781
|
+
module_name = None
|
|
1782
|
+
route_module = None
|
|
1783
|
+
manifest_dir_str = None
|
|
1784
|
+
path_inserted = False
|
|
1785
|
+
|
|
1786
|
+
for route_module_path in route_module_paths:
|
|
1787
|
+
if not route_module_path.exists():
|
|
1788
|
+
continue
|
|
1789
|
+
|
|
1790
|
+
# Create a unique module name to avoid conflicts
|
|
1791
|
+
module_name = f"mdb_engine_imported_routes_{slug}_{id(child_app)}"
|
|
1792
|
+
|
|
1793
|
+
try:
|
|
1794
|
+
# Validate file is actually a Python file
|
|
1795
|
+
if not route_module_path.suffix == ".py":
|
|
1796
|
+
logger.debug(f"Skipping non-Python file '{route_module_path}' for app '{slug}'")
|
|
1797
|
+
continue
|
|
1798
|
+
|
|
1799
|
+
# Load the module spec
|
|
1800
|
+
spec = importlib.util.spec_from_file_location(module_name, route_module_path)
|
|
1801
|
+
if spec is None or spec.loader is None:
|
|
1802
|
+
logger.warning(
|
|
1803
|
+
f"Could not create spec for route module '{route_module_path}' "
|
|
1804
|
+
f"for app '{slug}'"
|
|
1805
|
+
)
|
|
1806
|
+
continue
|
|
1807
|
+
|
|
1808
|
+
route_module = importlib.util.module_from_spec(spec)
|
|
1809
|
+
|
|
1810
|
+
# CRITICAL: Inject child_app into module namespace BEFORE loading
|
|
1811
|
+
# This ensures that @app.get(), @app.post(), etc. decorators in the
|
|
1812
|
+
# imported module will reference our child_app instead of creating a new one
|
|
1813
|
+
route_module.app = child_app
|
|
1814
|
+
route_module.engine = self # Also provide engine reference for dependencies
|
|
1815
|
+
|
|
1816
|
+
# Add to sys.modules temporarily to handle relative imports
|
|
1817
|
+
# Use a try-finally to ensure cleanup even on exceptions
|
|
1818
|
+
sys.modules[module_name] = route_module
|
|
1819
|
+
|
|
1820
|
+
# Store route count before import
|
|
1821
|
+
routes_before = len(child_app.routes)
|
|
1822
|
+
|
|
1823
|
+
# Add manifest directory to Python path temporarily for relative imports
|
|
1824
|
+
# This allows route modules to import sibling modules
|
|
1825
|
+
manifest_dir_str = str(manifest_dir.resolve())
|
|
1826
|
+
path_inserted = manifest_dir_str not in sys.path
|
|
1827
|
+
if path_inserted:
|
|
1828
|
+
sys.path.insert(0, manifest_dir_str)
|
|
1829
|
+
|
|
1830
|
+
try:
|
|
1831
|
+
# Execute the module (runs route decorators with injected app)
|
|
1832
|
+
spec.loader.exec_module(route_module)
|
|
1833
|
+
except SyntaxError as e:
|
|
1834
|
+
logger.warning(
|
|
1835
|
+
f"Syntax error in route module '{route_module_path}' "
|
|
1836
|
+
f"for app '{slug}': {e}. Skipping this module."
|
|
1837
|
+
)
|
|
1838
|
+
continue
|
|
1839
|
+
except ImportError as e:
|
|
1840
|
+
# ImportError might be due to missing dependencies - log but don't fail
|
|
1841
|
+
logger.debug(
|
|
1842
|
+
f"Import error in route module '{route_module_path}' "
|
|
1843
|
+
f"for app '{slug}': {e}. "
|
|
1844
|
+
"This may be OK if dependencies are optional."
|
|
1845
|
+
)
|
|
1846
|
+
# Check if it's a critical import (like FastAPI) vs optional dependency
|
|
1847
|
+
error_str = str(e).lower()
|
|
1848
|
+
if "fastapi" in error_str or "starlette" in error_str:
|
|
1849
|
+
logger.warning(
|
|
1850
|
+
f"Route module '{route_module_path}' for app '{slug}' "
|
|
1851
|
+
"requires FastAPI/Starlette but they're not available. "
|
|
1852
|
+
"Routes will not be registered."
|
|
1853
|
+
)
|
|
1854
|
+
continue
|
|
1855
|
+
finally:
|
|
1856
|
+
# Remove from path only if we added it
|
|
1857
|
+
if path_inserted and manifest_dir_str and manifest_dir_str in sys.path:
|
|
1858
|
+
try:
|
|
1859
|
+
sys.path.remove(manifest_dir_str)
|
|
1860
|
+
except ValueError:
|
|
1861
|
+
# Path might have been removed already - ignore
|
|
1862
|
+
pass
|
|
1863
|
+
|
|
1864
|
+
# Check if module overwrote app (shouldn't happen in well-structured modules)
|
|
1865
|
+
module_app = getattr(route_module, "app", None)
|
|
1866
|
+
if module_app is not None and module_app is not child_app:
|
|
1867
|
+
import warnings
|
|
1868
|
+
|
|
1869
|
+
warning_msg = (
|
|
1870
|
+
f"Route module '{route_module_path.name}' for app '{slug}' "
|
|
1871
|
+
"created its own app instance. Routes defined before app creation "
|
|
1872
|
+
"are registered, but routes defined after may not be. "
|
|
1873
|
+
"Consider restructuring the module to use the injected 'app' variable."
|
|
1874
|
+
)
|
|
1875
|
+
logger.warning(warning_msg)
|
|
1876
|
+
warnings.warn(warning_msg, UserWarning, stacklevel=2)
|
|
1877
|
+
|
|
1878
|
+
routes_after = len(child_app.routes)
|
|
1879
|
+
routes_added = routes_after - routes_before
|
|
1880
|
+
|
|
1881
|
+
if routes_added > 0:
|
|
1882
|
+
logger.info(
|
|
1883
|
+
f"✅ Auto-imported routes from '{route_module_path.name}' "
|
|
1884
|
+
f"for app '{slug}'. Added {routes_added} route(s) "
|
|
1885
|
+
f"(total: {routes_after})"
|
|
1886
|
+
)
|
|
1887
|
+
else:
|
|
1888
|
+
logger.debug(
|
|
1889
|
+
f"Route module '{route_module_path.name}' for app '{slug}' "
|
|
1890
|
+
"was imported but no new routes were registered. "
|
|
1891
|
+
"This may be expected if routes are registered conditionally."
|
|
1892
|
+
)
|
|
1893
|
+
|
|
1894
|
+
imported = True
|
|
1895
|
+
break
|
|
1896
|
+
|
|
1897
|
+
except (ValueError, TypeError, AttributeError, RuntimeError, OSError) as e:
|
|
1898
|
+
logger.warning(
|
|
1899
|
+
f"Unexpected error importing route module '{route_module_path}' "
|
|
1900
|
+
f"for app '{slug}': {e}",
|
|
1901
|
+
exc_info=True,
|
|
1902
|
+
)
|
|
1903
|
+
continue
|
|
1904
|
+
finally:
|
|
1905
|
+
# Clean up temporary module from sys.modules
|
|
1906
|
+
if module_name and module_name in sys.modules:
|
|
1907
|
+
try:
|
|
1908
|
+
del sys.modules[module_name]
|
|
1909
|
+
except KeyError:
|
|
1910
|
+
# Already removed - ignore
|
|
1911
|
+
pass
|
|
1912
|
+
# Ensure path is cleaned up even if exception occurred
|
|
1913
|
+
if path_inserted and manifest_dir_str and manifest_dir_str in sys.path:
|
|
1914
|
+
try:
|
|
1915
|
+
sys.path.remove(manifest_dir_str)
|
|
1916
|
+
except ValueError:
|
|
1917
|
+
pass
|
|
1918
|
+
|
|
1919
|
+
if not imported:
|
|
1920
|
+
logger.debug(
|
|
1921
|
+
f"No route modules found for app '{slug}' in {manifest_dir}. "
|
|
1922
|
+
"Routes may be defined elsewhere or app may not have HTTP routes."
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1732
1925
|
def create_multi_app( # noqa: C901
|
|
1733
1926
|
self,
|
|
1734
1927
|
apps: list[dict[str, Any]] | None = None,
|
|
@@ -2059,6 +2252,26 @@ class MongoDBEngine:
|
|
|
2059
2252
|
on_shutdown=on_shutdown,
|
|
2060
2253
|
)
|
|
2061
2254
|
|
|
2255
|
+
# Automatically import routes from app module
|
|
2256
|
+
# This discovers and imports route modules (web.py, routes.py, etc.)
|
|
2257
|
+
# so that route decorators are executed and routes are registered
|
|
2258
|
+
try:
|
|
2259
|
+
self._import_app_routes(child_app, manifest_path, slug)
|
|
2260
|
+
except (
|
|
2261
|
+
ValueError,
|
|
2262
|
+
TypeError,
|
|
2263
|
+
AttributeError,
|
|
2264
|
+
RuntimeError,
|
|
2265
|
+
ImportError,
|
|
2266
|
+
SyntaxError,
|
|
2267
|
+
OSError,
|
|
2268
|
+
) as e:
|
|
2269
|
+
logger.warning(
|
|
2270
|
+
f"Failed to auto-import routes for app '{slug}': {e}. "
|
|
2271
|
+
"Routes may need to be imported manually.",
|
|
2272
|
+
exc_info=True,
|
|
2273
|
+
)
|
|
2274
|
+
|
|
2062
2275
|
# Share user_pool with child app if shared auth is enabled
|
|
2063
2276
|
if shared_user_pool_initialized and hasattr(app.state, "user_pool"):
|
|
2064
2277
|
child_app.state.user_pool = app.state.user_pool
|
|
@@ -2081,6 +2294,8 @@ class MongoDBEngine:
|
|
|
2081
2294
|
# Store parent app reference and current app info for middleware
|
|
2082
2295
|
child_app.state.parent_app = app
|
|
2083
2296
|
child_app.state.app_slug = slug
|
|
2297
|
+
# Required for get_scoped_db and other dependencies
|
|
2298
|
+
child_app.state.engine = engine
|
|
2084
2299
|
child_app.state.app_base_path = path_prefix
|
|
2085
2300
|
child_app.state.app_auth_hub_url = auth_hub_url
|
|
2086
2301
|
child_app.state.app_manifest = app_manifest_data
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mdb_engine/README.md,sha256=T3EFGcPopY9LslYW3lxgG3hohWkAOmBNbYG0FDMUJiY,3502
|
|
2
|
-
mdb_engine/__init__.py,sha256=
|
|
2
|
+
mdb_engine/__init__.py,sha256=Hm7dL74-37z0tXYLcgCV5f6CZ_ZDOaTACYw4N863OCA,3262
|
|
3
3
|
mdb_engine/config.py,sha256=DTAyxfKB8ogyI0v5QR9Y-SJOgXQr_eDBCKxNBSqEyLc,7269
|
|
4
4
|
mdb_engine/constants.py,sha256=eaotvW57TVOg7rRbLziGrVNoP7adgw_G9iVByHezc_A,7837
|
|
5
5
|
mdb_engine/dependencies.py,sha256=MJuYQhZ9ZGzXlip1ha5zba9Rvn04HDPWahJFJH81Q2s,14107
|
|
@@ -46,7 +46,7 @@ mdb_engine/core/app_registration.py,sha256=7szt2a7aBkpSppjmhdkkPPYMKGKo0MkLKZeEe
|
|
|
46
46
|
mdb_engine/core/app_secrets.py,sha256=bo-syg9UUATibNyXEZs-0TTYWG-JaY-2S0yNSGA12n0,10524
|
|
47
47
|
mdb_engine/core/connection.py,sha256=XnwuPG34pJ7kJGJ84T0mhj1UZ6_CLz_9qZf6NRYGIS8,8346
|
|
48
48
|
mdb_engine/core/encryption.py,sha256=RZ5LPF5g28E3ZBn6v1IMw_oas7u9YGFtBcEj8lTi9LM,7515
|
|
49
|
-
mdb_engine/core/engine.py,sha256=
|
|
49
|
+
mdb_engine/core/engine.py,sha256=CAhewr0DWaakRKf8-NSSni_pWU3AFdIVLNBTgzGR4IQ,133549
|
|
50
50
|
mdb_engine/core/index_management.py,sha256=9-r7MIy3JnjQ35sGqsbj8K_I07vAUWtAVgSWC99lJcE,5555
|
|
51
51
|
mdb_engine/core/manifest.py,sha256=jguhjVPAHMZGxOJcdSGouv9_XiKmxUjDmyjn2yXHCj4,139205
|
|
52
52
|
mdb_engine/core/ray_integration.py,sha256=vexYOzztscvRYje1xTNmXJbi99oJxCaVJAwKfTNTF_E,13610
|
|
@@ -89,9 +89,9 @@ mdb_engine/routing/__init__.py,sha256=reupjHi_RTc2ZBA4AH5XzobAmqy4EQIsfSUcTkFknU
|
|
|
89
89
|
mdb_engine/routing/websockets.py,sha256=3X4OjQv_Nln4UmeifJky0gFhMG8A6alR77I8g1iIOLY,29311
|
|
90
90
|
mdb_engine/utils/__init__.py,sha256=lDxQSGqkV4fVw5TWIk6FA6_eey_ZnEtMY0fir3cpAe8,236
|
|
91
91
|
mdb_engine/utils/mongo.py,sha256=Oqtv4tQdpiiZzrilGLEYQPo8Vmh8WsTQypxQs8Of53s,3369
|
|
92
|
-
mdb_engine-0.4.
|
|
93
|
-
mdb_engine-0.4.
|
|
94
|
-
mdb_engine-0.4.
|
|
95
|
-
mdb_engine-0.4.
|
|
96
|
-
mdb_engine-0.4.
|
|
97
|
-
mdb_engine-0.4.
|
|
92
|
+
mdb_engine-0.4.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
93
|
+
mdb_engine-0.4.4.dist-info/METADATA,sha256=-VlUy-pbstZzXVVyb3oaKJAecjYQ_rMMN_iH_Npji7s,15810
|
|
94
|
+
mdb_engine-0.4.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
95
|
+
mdb_engine-0.4.4.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
|
|
96
|
+
mdb_engine-0.4.4.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
|
|
97
|
+
mdb_engine-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|