Skip to content

Commit

Permalink
Fix mypy for pydantic v1 and v2
Browse files Browse the repository at this point in the history
This required that we allow unused igores since the location
of the ignores depends on the version of pydantic and mypy
always picks up the first definition it sees even if that
is a pydantic V2 code path and pydantic is v1.
  • Loading branch information
timj committed Jul 18, 2023
1 parent e96015d commit 92a5351
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 8 deletions.
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ disallow_untyped_defs = True
disallow_incomplete_defs = True
strict_equality = True
warn_unreachable = True
warn_unused_ignores = True
warn_unused_ignores = False

# ...except the modules and subpackages below (can't find a way to do line
# breaks in the lists of modules).
Expand Down
51 changes: 46 additions & 5 deletions python/lsst/daf/butler/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import sys
from collections.abc import Callable
from typing import Any
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
Expand Down Expand Up @@ -54,8 +54,8 @@ class _BaseModelCompat(BaseModel):
def json(
self,
*,
include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None,
exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None,
include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, # type: ignore
exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, # type: ignore
by_alias: bool = False,
skip_defaults: bool | None = None,
exclude_unset: bool = False,
Expand Down Expand Up @@ -84,9 +84,46 @@ def parse_obj(cls, obj: Any) -> Self:
# Catch warnings and call BaseModel.parse_obj directly?
return cls.model_validate(obj)

if TYPE_CHECKING and not PYDANTIC_V2:
# mypy sees the first definition of a class and ignores any
# redefinition. This means that if mypy is run with pydantic v1
# it will not see the classes defined in the else block below.

@classmethod
def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self:
return cls()

@classmethod
def model_validate(
cls,
obj: Any,
*,
strict: bool | None = None,
from_attributes: bool | None = None,
context: dict[str, Any] | None = None,
) -> Self:
return cls()

def model_dump_json(
self,
*,
indent: int | None = None,
include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None,
exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None,
by_alias: bool = False,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
round_trip: bool = False,
warnings: bool = True,
) -> str:
return ""

else:

class _BaseModelCompat(BaseModel): # type:ignore[no-redef]
"""Methods from pydantic v2 that can be used in pydantic v1."""

@classmethod
def model_validate(
cls,
Expand All @@ -112,10 +149,14 @@ def model_dump_json(
warnings: bool = True,
) -> str:
return self.json(
include=include,
exclude=exclude,
include=include, # type: ignore
exclude=exclude, # type: ignore
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)

@classmethod # type: ignore
def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self:
return cls.construct(_fields_set=_fields_set, **values)
2 changes: 1 addition & 1 deletion python/lsst/daf/butler/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def __str__(self) -> str:


if PYDANTIC_V2:
from pydantic import RootModel
from pydantic import RootModel # type: ignore

class _ButlerLogRecords(RootModel):
root: list[ButlerLogRecord]
Expand Down
2 changes: 1 addition & 1 deletion python/lsst/daf/butler/registry/wildcards.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def process(element: Any, alreadyCoerced: bool = False) -> EllipsisType | None:


if PYDANTIC_V2:
from pydantic import RootModel
from pydantic import RootModel # type: ignore

class _CollectionSearch(RootModel):
root: tuple[str, ...]
Expand Down

0 comments on commit 92a5351

Please sign in to comment.