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 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.2" # Security release: Fixed auto-role assignment, token blacklist
86
- # fail-closed, race conditions, session binding, and path traversal
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdb-engine
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
6
  Author: Fabian Valle
@@ -1,5 +1,5 @@
1
1
  mdb_engine/README.md,sha256=T3EFGcPopY9LslYW3lxgG3hohWkAOmBNbYG0FDMUJiY,3502
2
- mdb_engine/__init__.py,sha256=6Uve36cIfvCcTfYTZEHKkZoD9sz4zw3Wfz76y0FEvUo,3242
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=ManZZUCGsI0mNlBKL7CxofX1L1tkJDNI4-8YHBKGYYk,123708
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.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
93
- mdb_engine-0.4.2.dist-info/METADATA,sha256=1wI8au6nvUczKwC6e-MoS7fvqHv-TozwjswYSg5TT6w,15810
94
- mdb_engine-0.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
95
- mdb_engine-0.4.2.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
96
- mdb_engine-0.4.2.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
97
- mdb_engine-0.4.2.dist-info/RECORD,,
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,,