Add support for cascade delete relationships: cascade_delete, ondelete, and passive_deletes (#983)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
Esteban Maya
2024-07-20 21:08:06 -05:00
committed by GitHub
parent 19c736766e
commit 86ab09f7ec
37 changed files with 4501 additions and 2 deletions

View File

@ -91,6 +91,7 @@ if TYPE_CHECKING:
_T = TypeVar("_T")
NoArgAnyCallable = Callable[[], Any]
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]
def __dataclass_transform__(
@ -108,6 +109,7 @@ class FieldInfo(PydanticFieldInfo):
primary_key = kwargs.pop("primary_key", False)
nullable = kwargs.pop("nullable", Undefined)
foreign_key = kwargs.pop("foreign_key", Undefined)
ondelete = kwargs.pop("ondelete", Undefined)
unique = kwargs.pop("unique", False)
index = kwargs.pop("index", Undefined)
sa_type = kwargs.pop("sa_type", Undefined)
@ -132,13 +134,17 @@ class FieldInfo(PydanticFieldInfo):
)
if nullable is not Undefined:
raise RuntimeError(
"Passing nullable is not supported when " "also passing a sa_column"
"Passing nullable is not supported when also passing a sa_column"
)
if foreign_key is not Undefined:
raise RuntimeError(
"Passing foreign_key is not supported when "
"also passing a sa_column"
)
if ondelete is not Undefined:
raise RuntimeError(
"Passing ondelete is not supported when also passing a sa_column"
)
if unique is not Undefined:
raise RuntimeError(
"Passing unique is not supported when also passing a sa_column"
@ -151,10 +157,14 @@ class FieldInfo(PydanticFieldInfo):
raise RuntimeError(
"Passing sa_type is not supported when also passing a sa_column"
)
if ondelete is not Undefined:
if foreign_key is Undefined:
raise RuntimeError("ondelete can only be used with foreign_key")
super().__init__(default=default, **kwargs)
self.primary_key = primary_key
self.nullable = nullable
self.foreign_key = foreign_key
self.ondelete = ondelete
self.unique = unique
self.index = index
self.sa_type = sa_type
@ -168,6 +178,8 @@ class RelationshipInfo(Representation):
self,
*,
back_populates: Optional[str] = None,
cascade_delete: Optional[bool] = False,
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
link_model: Optional[Any] = None,
sa_relationship: Optional[RelationshipProperty] = None, # type: ignore
sa_relationship_args: Optional[Sequence[Any]] = None,
@ -185,12 +197,15 @@ class RelationshipInfo(Representation):
"also passing a sa_relationship"
)
self.back_populates = back_populates
self.cascade_delete = cascade_delete
self.passive_deletes = passive_deletes
self.link_model = link_model
self.sa_relationship = sa_relationship
self.sa_relationship_args = sa_relationship_args
self.sa_relationship_kwargs = sa_relationship_kwargs
# include sa_type, sa_column_args, sa_column_kwargs
@overload
def Field(
default: Any = Undefined,
@ -234,6 +249,62 @@ def Field(
) -> Any: ...
# When foreign_key is str, include ondelete
# include sa_type, sa_column_args, sa_column_kwargs
@overload
def Field(
default: Any = Undefined,
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
include: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
const: Optional[bool] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
multiple_of: Optional[float] = None,
max_digits: Optional[int] = None,
decimal_places: Optional[int] = None,
min_items: Optional[int] = None,
max_items: Optional[int] = None,
unique_items: Optional[bool] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
allow_mutation: bool = True,
regex: Optional[str] = None,
discriminator: Optional[str] = None,
repr: bool = True,
primary_key: Union[bool, UndefinedType] = Undefined,
foreign_key: str,
ondelete: Union[OnDeleteType, UndefinedType] = Undefined,
unique: Union[bool, UndefinedType] = Undefined,
nullable: Union[bool, UndefinedType] = Undefined,
index: Union[bool, UndefinedType] = Undefined,
sa_type: Union[Type[Any], UndefinedType] = Undefined,
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
) -> Any: ...
# Include sa_column, don't include
# primary_key
# foreign_key
# ondelete
# unique
# nullable
# index
# sa_type
# sa_column_args
# sa_column_kwargs
@overload
def Field(
default: Any = Undefined,
@ -302,6 +373,7 @@ def Field(
repr: bool = True,
primary_key: Union[bool, UndefinedType] = Undefined,
foreign_key: Any = Undefined,
ondelete: Union[OnDeleteType, UndefinedType] = Undefined,
unique: Union[bool, UndefinedType] = Undefined,
nullable: Union[bool, UndefinedType] = Undefined,
index: Union[bool, UndefinedType] = Undefined,
@ -339,6 +411,7 @@ def Field(
repr=repr,
primary_key=primary_key,
foreign_key=foreign_key,
ondelete=ondelete,
unique=unique,
nullable=nullable,
index=index,
@ -356,6 +429,8 @@ def Field(
def Relationship(
*,
back_populates: Optional[str] = None,
cascade_delete: Optional[bool] = False,
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
link_model: Optional[Any] = None,
sa_relationship_args: Optional[Sequence[Any]] = None,
sa_relationship_kwargs: Optional[Mapping[str, Any]] = None,
@ -366,6 +441,8 @@ def Relationship(
def Relationship(
*,
back_populates: Optional[str] = None,
cascade_delete: Optional[bool] = False,
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
link_model: Optional[Any] = None,
sa_relationship: Optional[RelationshipProperty[Any]] = None,
) -> Any: ...
@ -374,6 +451,8 @@ def Relationship(
def Relationship(
*,
back_populates: Optional[str] = None,
cascade_delete: Optional[bool] = False,
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
link_model: Optional[Any] = None,
sa_relationship: Optional[RelationshipProperty[Any]] = None,
sa_relationship_args: Optional[Sequence[Any]] = None,
@ -381,6 +460,8 @@ def Relationship(
) -> Any:
relationship_info = RelationshipInfo(
back_populates=back_populates,
cascade_delete=cascade_delete,
passive_deletes=passive_deletes,
link_model=link_model,
sa_relationship=sa_relationship,
sa_relationship_args=sa_relationship_args,
@ -531,6 +612,10 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
rel_kwargs: Dict[str, Any] = {}
if rel_info.back_populates:
rel_kwargs["back_populates"] = rel_info.back_populates
if rel_info.cascade_delete:
rel_kwargs["cascade"] = "all, delete-orphan"
if rel_info.passive_deletes:
rel_kwargs["passive_deletes"] = rel_info.passive_deletes
if rel_info.link_model:
ins = inspect(rel_info.link_model)
local_table = getattr(ins, "local_table") # noqa: B009
@ -642,8 +727,14 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
if unique is Undefined:
unique = False
if foreign_key:
if field_info.ondelete == "SET NULL" and not nullable:
raise RuntimeError('ondelete="SET NULL" requires nullable=True')
assert isinstance(foreign_key, str)
args.append(ForeignKey(foreign_key))
ondelete = getattr(field_info, "ondelete", Undefined)
if ondelete is Undefined:
ondelete = None
assert isinstance(ondelete, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete))
kwargs = {
"primary_key": primary_key,
"nullable": nullable,