Forráskód Böngészése

Force pydantic v1 for sqlmodel compatibility (#3026)

Masen Furer 1 éve
szülő
commit
d7abcd45de

+ 6 - 0
.github/workflows/unit_tests.yml

@@ -76,4 +76,10 @@ jobs:
           export PYTHONUNBUFFERED=1
           export PYTHONUNBUFFERED=1
           export REDIS_URL=redis://localhost:6379
           export REDIS_URL=redis://localhost:6379
           poetry run pytest tests --cov --no-cov-on-fail --cov-report=
           poetry run pytest tests --cov --no-cov-on-fail --cov-report=
+      # Change to explicitly install v1 when reflex-hosting-cli is compatible with v2
+      - name: Run unit tests w/ pydantic v2
+        run: |
+          export PYTHONUNBUFFERED=1
+          poetry run pip install "pydantic>2"
+          poetry run pytest tests --cov --no-cov-on-fail --cov-report=
       - run: poetry run coverage html
       - run: poetry run coverage html

+ 1 - 1
reflex/compiler/utils.py

@@ -11,7 +11,7 @@ try:
     # reflex-hosting-cli tools are compatible with pydantic v2
     # reflex-hosting-cli tools are compatible with pydantic v2
 
 
     if not TYPE_CHECKING:
     if not TYPE_CHECKING:
-        import pydantic.v1.fields as ModelField
+        from pydantic.v1.fields import ModelField
     else:
     else:
         raise ModuleNotFoundError
         raise ModuleNotFoundError
 except ModuleNotFoundError:
 except ModuleNotFoundError:

+ 1 - 1
reflex/model.py

@@ -16,12 +16,12 @@ import alembic.script
 import alembic.util
 import alembic.util
 import sqlalchemy
 import sqlalchemy
 import sqlalchemy.orm
 import sqlalchemy.orm
-import sqlmodel
 
 
 from reflex import constants
 from reflex import constants
 from reflex.base import Base
 from reflex.base import Base
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.utils import console
 from reflex.utils import console
+from reflex.utils.compat import sqlmodel
 
 
 
 
 def get_engine(url: str | None = None):
 def get_engine(url: str | None = None):

+ 43 - 0
reflex/utils/compat.py

@@ -0,0 +1,43 @@
+"""Compatibility hacks and helpers."""
+
+import contextlib
+import sys
+
+
+@contextlib.contextmanager
+def pydantic_v1_patch():
+    """A context manager that patches the Pydantic module to mimic v1 behaviour.
+
+    Yields:
+        None when the Pydantic module is patched.
+    """
+    patched_modules = [
+        "pydantic",
+        "pydantic.fields",
+        "pydantic.errors",
+        "pydantic.main",
+    ]
+    originals = {module: sys.modules.get(module) for module in patched_modules}
+    try:
+        import pydantic.v1  # type: ignore
+
+        sys.modules["pydantic.fields"] = pydantic.v1.fields  # type: ignore
+        sys.modules["pydantic.main"] = pydantic.v1.main  # type: ignore
+        sys.modules["pydantic.errors"] = pydantic.v1.errors  # type: ignore
+        sys.modules["pydantic"] = pydantic.v1
+        yield
+    except (ImportError, AttributeError):
+        # pydantic v1 is already installed
+        yield
+    finally:
+        # Restore the original Pydantic module
+        for k, original in originals.items():
+            if k in sys.modules:
+                if original:
+                    sys.modules[k] = original
+                else:
+                    del sys.modules[k]
+
+
+with pydantic_v1_patch():
+    import sqlmodel as sqlmodel

+ 1 - 1
reflex/utils/types.py

@@ -29,7 +29,7 @@ try:
     # reflex-hosting-cli tools are compatible with pydantic v2
     # reflex-hosting-cli tools are compatible with pydantic v2
 
 
     if not TYPE_CHECKING:
     if not TYPE_CHECKING:
-        import pydantic.v1.fields as ModelField
+        from pydantic.v1.fields import ModelField
     else:
     else:
         raise ModuleNotFoundError
         raise ModuleNotFoundError
 except ModuleNotFoundError:
 except ModuleNotFoundError:

+ 6 - 1
tests/components/core/test_foreach.py

@@ -1,12 +1,17 @@
 from typing import Dict, List, Set, Tuple
 from typing import Dict, List, Set, Tuple
 
 
 import pytest
 import pytest
-from pydantic import ValidationError
 
 
 from reflex.components import box, foreach, text, theme
 from reflex.components import box, foreach, text, theme
 from reflex.components.core import Foreach
 from reflex.components.core import Foreach
 from reflex.state import BaseState
 from reflex.state import BaseState
 
 
+try:
+    # When pydantic v2 is installed
+    from pydantic.v1 import ValidationError  # type: ignore
+except ImportError:
+    from pydantic import ValidationError
+
 
 
 class ForEachState(BaseState):
 class ForEachState(BaseState):
     """A state for testing the ForEach component."""
     """A state for testing the ForEach component."""