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 REDIS_URL=redis://localhost:6379
           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

+ 1 - 1
reflex/compiler/utils.py

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

+ 1 - 1
reflex/model.py

@@ -16,12 +16,12 @@ import alembic.script
 import alembic.util
 import sqlalchemy
 import sqlalchemy.orm
-import sqlmodel
 
 from reflex import constants
 from reflex.base import Base
 from reflex.config import get_config
 from reflex.utils import console
+from reflex.utils.compat import sqlmodel
 
 
 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
 
     if not TYPE_CHECKING:
-        import pydantic.v1.fields as ModelField
+        from pydantic.v1.fields import ModelField
     else:
         raise ModuleNotFoundError
 except ModuleNotFoundError:

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

@@ -1,12 +1,17 @@
 from typing import Dict, List, Set, Tuple
 
 import pytest
-from pydantic import ValidationError
 
 from reflex.components import box, foreach, text, theme
 from reflex.components.core import Foreach
 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):
     """A state for testing the ForEach component."""