From 3331bbc4a6f124ec14b043f083ce7b67790b6fb7 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Thu, 20 Nov 2025 16:11:01 -0800 Subject: [PATCH 1/3] Add GH workflow --- .github/workflows/docs-pages.yml | 72 ++++++++++++++++++++++++++++++++ mkdocs.yml | 26 ++++++++++++ pyproject.toml | 1 + 3 files changed, 99 insertions(+) create mode 100644 .github/workflows/docs-pages.yml create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml new file mode 100644 index 00000000..ef23559b --- /dev/null +++ b/.github/workflows/docs-pages.yml @@ -0,0 +1,72 @@ +name: Docs: Build and deploy MkDocs site + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@v4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + + - name: Install library + run: poetry install --no-interaction + + - name: Build MkDocs site + run: poetry run mkdocs build + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload built site artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..7a27b093 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,26 @@ +site_name: Redis OM Python +site_description: Object mapping, and more, for Redis and Python. +site_url: https://redis.github.io/redis-om-python/ +repo_url: https://github.com/redis/redis-om-python +repo_name: redis-om-python + +# Source Markdown lives in the existing docs/ directory. +docs_dir: docs + +# Use the built-in MkDocs theme for now. This avoids extra dependencies. +theme: + name: mkdocs + +nav: + - Home: index.md + - Getting started: getting_started.md + - Models: models.md + - Connections: connections.md + - Validation: validation.md + - Redis modules: redis_modules.md + - Migrations: + - Overview: migrations.md + - Upgrade 0.x to 1.x: migration_guide_0x_to_1x.md + - FastAPI integration: fastapi_integration.md + - Errors: errors.md + diff --git a/pyproject.toml b/pyproject.toml index c85857f0..9dccb0a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ tox = "^4.14.1" tox-pyenv = "^1.1.0" codespell = "^2.2.0" pre-commit = {version = "^4.3.0", python = ">=3.9"} +mkdocs = "^1.6.1" [tool.poetry.scripts] # Unified CLI (new, recommended) - uses async components From 57607d504da81deedd2c3ddbb4421b6db1a229ca Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Fri, 5 Dec 2025 17:48:31 -0800 Subject: [PATCH 2/3] Fix Pydantic 2.12+ compatibility for custom FieldInfo with Annotated types Pydantic 2.12+ converts custom FieldInfo subclasses to plain PydanticFieldInfo for fields using Annotated types with metadata (like Coordinates). This caused custom attributes like index, sortable, etc. to be lost. Fix: Capture original FieldInfo objects before Pydantic processes them and restore them when Pydantic has converted them to plain PydanticFieldInfo. --- aredis_om/model/model.py | 29 ++++++++++++++++++++++++++++- tests/test_json_model.py | 12 +++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index 24af4781..bf69acfb 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -2068,6 +2068,15 @@ class ModelMeta(ModelMetaclass): def __new__(cls, name, bases, attrs, **kwargs): # noqa C901 meta = attrs.pop("Meta", None) + # Capture original FieldInfo objects from attrs before Pydantic processes them. + # Pydantic 2.12+ may convert custom FieldInfo subclasses to plain PydanticFieldInfo + # for Annotated types, losing custom attributes like index, sortable, etc. + original_field_infos: Dict[str, FieldInfo] = {} + if PYDANTIC_V2: + for attr_name, attr_value in attrs.items(): + if isinstance(attr_value, FieldInfo): + original_field_infos[attr_name] = attr_value + # Duplicate logic from Pydantic to filter config kwargs because if they are # passed directly including the registry Pydantic will pass them over to the # superclass causing an error @@ -2141,13 +2150,31 @@ class Config: model_fields = new_class.__fields__ for field_name, field in model_fields.items(): + pydantic_field = field # Keep reference to Pydantic's processed field if type(field) is PydanticFieldInfo: + # Pydantic converted our FieldInfo to a plain PydanticFieldInfo. + # This happens with Annotated types in Pydantic 2.12+. + # Use the original FieldInfo if we captured it, otherwise create a new one. if PYDANTIC_V2: - field = FieldInfo(**field._attributes_set) + if field_name in original_field_infos: + # Use the original FieldInfo with custom attributes preserved + field = original_field_infos[field_name] + # Copy the annotation from Pydantic's processed field + # since it wasn't set on the original FieldInfo + if hasattr(pydantic_field, "annotation"): + field.annotation = pydantic_field.annotation + # Also copy metadata from Pydantic's field (validators, serializers, etc.) + if hasattr(pydantic_field, "metadata"): + field.metadata = pydantic_field.metadata + else: + field = FieldInfo(**field._attributes_set) else: # Pydantic v1 compatibility field = FieldInfo() setattr(new_class, field_name, field) + # Also update model_fields so schema generation uses our fixed field + if PYDANTIC_V2: + model_fields[field_name] = field if is_indexed: setattr(new_class, field_name, ExpressionProxy(field, [])) diff --git a/tests/test_json_model.py b/tests/test_json_model.py index d59a30ee..5a3d5ed1 100644 --- a/tests/test_json_model.py +++ b/tests/test_json_model.py @@ -1528,7 +1528,9 @@ class Product(JsonModel, index=True): name: str = Field(index=True, sortable=True) # TAG field with sortable category: str = Field(index=True, sortable=True) # TAG field with sortable price: int = Field(index=True, sortable=True) # NUMERIC field with sortable - tags: List[str] = Field(index=True, sortable=True) # TAG field (list) with sortable + tags: List[str] = Field( + index=True, sortable=True + ) # TAG field (list) with sortable class Meta: global_key_prefix = key_prefix @@ -1543,9 +1545,13 @@ class Meta: await Migrator().run() # Create test data - product1 = Product(name="Zebra", category="Animals", price=100, tags=["wild", "africa"]) + product1 = Product( + name="Zebra", category="Animals", price=100, tags=["wild", "africa"] + ) product2 = Product(name="Apple", category="Fruits", price=50, tags=["red", "sweet"]) - product3 = Product(name="Banana", category="Fruits", price=30, tags=["yellow", "sweet"]) + product3 = Product( + name="Banana", category="Fruits", price=30, tags=["yellow", "sweet"] + ) await product1.save() await product2.save() From 77bcfded6213b63b247d65b20efc7503782354f2 Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Mon, 8 Dec 2025 11:38:06 -0800 Subject: [PATCH 3/3] Exclude mypy on PyPy to avoid librt build failure mypy 1.19+ depends on librt which only has CPython wheels. When Poetry tries to install on PyPy, it falls back to building from source, which fails because librt uses CPython-specific C APIs. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9dccb0a9..90d1efb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ setuptools = ">=70.0" pydantic-extra-types = "^2.10.5" [tool.poetry.group.dev.dependencies] -mypy = "^1.9.0" +mypy = {version = "^1.9.0", markers = "platform_python_implementation == 'CPython'"} pytest = "^8.0.2" ipdb = "^0.13.9" black = "^24.2"