_compat.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. import types
  2. from contextlib import contextmanager
  3. from contextvars import ContextVar
  4. from dataclasses import dataclass
  5. from typing import (
  6. TYPE_CHECKING,
  7. AbstractSet,
  8. Any,
  9. Callable,
  10. Dict,
  11. ForwardRef,
  12. Generator,
  13. Mapping,
  14. Optional,
  15. Set,
  16. Type,
  17. TypeVar,
  18. Union,
  19. )
  20. from pydantic import VERSION as P_VERSION
  21. from pydantic import BaseModel
  22. from pydantic.fields import FieldInfo
  23. from typing_extensions import Annotated, get_args, get_origin
  24. # Reassign variable to make it reexported for mypy
  25. PYDANTIC_VERSION = P_VERSION
  26. IS_PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
  27. if TYPE_CHECKING:
  28. from .main import RelationshipInfo, SQLModel
  29. UnionType = getattr(types, "UnionType", Union)
  30. NoneType = type(None)
  31. T = TypeVar("T")
  32. InstanceOrType = Union[T, Type[T]]
  33. _TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
  34. class FakeMetadata:
  35. max_length: Optional[int] = None
  36. max_digits: Optional[int] = None
  37. decimal_places: Optional[int] = None
  38. @dataclass
  39. class ObjectWithUpdateWrapper:
  40. obj: Any
  41. update: Dict[str, Any]
  42. def __getattribute__(self, __name: str) -> Any:
  43. update = super().__getattribute__("update")
  44. obj = super().__getattribute__("obj")
  45. if __name in update:
  46. return update[__name]
  47. return getattr(obj, __name)
  48. def _is_union_type(t: Any) -> bool:
  49. return t is UnionType or t is Union
  50. finish_init: ContextVar[bool] = ContextVar("finish_init", default=True)
  51. @contextmanager
  52. def partial_init() -> Generator[None, None, None]:
  53. token = finish_init.set(False)
  54. yield
  55. finish_init.reset(token)
  56. if IS_PYDANTIC_V2:
  57. from annotated_types import MaxLen
  58. from pydantic import ConfigDict as BaseConfig
  59. from pydantic._internal._fields import PydanticMetadata
  60. from pydantic._internal._model_construction import ModelMetaclass
  61. from pydantic._internal._repr import Representation as Representation
  62. from pydantic_core import PydanticUndefined as Undefined
  63. from pydantic_core import PydanticUndefinedType as UndefinedType
  64. # Dummy for types, to make it importable
  65. class ModelField:
  66. pass
  67. class SQLModelConfig(BaseConfig, total=False):
  68. table: Optional[bool]
  69. registry: Optional[Any]
  70. def get_config_value(
  71. *, model: InstanceOrType["SQLModel"], parameter: str, default: Any = None
  72. ) -> Any:
  73. return model.model_config.get(parameter, default)
  74. def set_config_value(
  75. *,
  76. model: InstanceOrType["SQLModel"],
  77. parameter: str,
  78. value: Any,
  79. ) -> None:
  80. model.model_config[parameter] = value # type: ignore[literal-required]
  81. def get_model_fields(model: InstanceOrType[BaseModel]) -> Dict[str, "FieldInfo"]:
  82. return model.model_fields
  83. def get_fields_set(
  84. object: InstanceOrType["SQLModel"],
  85. ) -> Union[Set[str], Callable[[BaseModel], Set[str]]]:
  86. return object.model_fields_set
  87. def init_pydantic_private_attrs(new_object: InstanceOrType["SQLModel"]) -> None:
  88. object.__setattr__(new_object, "__pydantic_fields_set__", set())
  89. object.__setattr__(new_object, "__pydantic_extra__", None)
  90. object.__setattr__(new_object, "__pydantic_private__", None)
  91. def get_annotations(class_dict: Dict[str, Any]) -> Dict[str, Any]:
  92. return class_dict.get("__annotations__", {})
  93. def is_table_model_class(cls: Type[Any]) -> bool:
  94. config = getattr(cls, "model_config", {})
  95. if config:
  96. return config.get("table", False) or False
  97. return False
  98. def get_relationship_to(
  99. name: str,
  100. rel_info: "RelationshipInfo",
  101. annotation: Any,
  102. ) -> Any:
  103. origin = get_origin(annotation)
  104. use_annotation = annotation
  105. # Direct relationships (e.g. 'Team' or Team) have None as an origin
  106. if origin is None:
  107. if isinstance(use_annotation, ForwardRef):
  108. use_annotation = use_annotation.__forward_arg__
  109. else:
  110. return use_annotation
  111. # If Union (e.g. Optional), get the real field
  112. elif _is_union_type(origin):
  113. use_annotation = get_args(annotation)
  114. if len(use_annotation) > 2:
  115. raise ValueError(
  116. "Cannot have a (non-optional) union as a SQLAlchemy field"
  117. )
  118. arg1, arg2 = use_annotation
  119. if arg1 is NoneType and arg2 is not NoneType:
  120. use_annotation = arg2
  121. elif arg2 is NoneType and arg1 is not NoneType:
  122. use_annotation = arg1
  123. else:
  124. raise ValueError(
  125. "Cannot have a Union of None and None as a SQLAlchemy field"
  126. )
  127. # If a list, then also get the real field
  128. elif origin is list:
  129. use_annotation = get_args(annotation)[0]
  130. return get_relationship_to(
  131. name=name, rel_info=rel_info, annotation=use_annotation
  132. )
  133. def is_field_noneable(field: "FieldInfo") -> bool:
  134. if getattr(field, "nullable", Undefined) is not Undefined:
  135. return field.nullable # type: ignore
  136. origin = get_origin(field.annotation)
  137. if origin is not None and _is_union_type(origin):
  138. args = get_args(field.annotation)
  139. if any(arg is NoneType for arg in args):
  140. return True
  141. if not field.is_required():
  142. if field.default is Undefined:
  143. return False
  144. if field.annotation is None or field.annotation is NoneType: # type: ignore[comparison-overlap]
  145. return True
  146. return False
  147. return False
  148. def get_sa_type_from_type_annotation(annotation: Any) -> Any:
  149. # Resolve Optional fields
  150. if annotation is None:
  151. raise ValueError("Missing field type")
  152. origin = get_origin(annotation)
  153. if origin is None:
  154. return annotation
  155. elif origin is Annotated:
  156. return get_sa_type_from_type_annotation(get_args(annotation)[0])
  157. if _is_union_type(origin):
  158. bases = get_args(annotation)
  159. if len(bases) > 2:
  160. raise ValueError(
  161. "Cannot have a (non-optional) union as a SQLAlchemy field"
  162. )
  163. # Non optional unions are not allowed
  164. if bases[0] is not NoneType and bases[1] is not NoneType:
  165. raise ValueError(
  166. "Cannot have a (non-optional) union as a SQLAlchemy field"
  167. )
  168. # Optional unions are allowed
  169. use_type = bases[0] if bases[0] is not NoneType else bases[1]
  170. return get_sa_type_from_type_annotation(use_type)
  171. return origin
  172. def get_sa_type_from_field(field: Any) -> Any:
  173. type_: Any = field.annotation
  174. return get_sa_type_from_type_annotation(type_)
  175. def get_field_metadata(field: Any) -> Any:
  176. for meta in field.metadata:
  177. if isinstance(meta, (PydanticMetadata, MaxLen)):
  178. return meta
  179. return FakeMetadata()
  180. def post_init_field_info(field_info: FieldInfo) -> None:
  181. return None
  182. # Dummy to make it importable
  183. def _calculate_keys(
  184. self: "SQLModel",
  185. include: Optional[Mapping[Union[int, str], Any]],
  186. exclude: Optional[Mapping[Union[int, str], Any]],
  187. exclude_unset: bool,
  188. update: Optional[Dict[str, Any]] = None,
  189. ) -> Optional[AbstractSet[str]]: # pragma: no cover
  190. return None
  191. def sqlmodel_table_construct(
  192. *,
  193. self_instance: _TSQLModel,
  194. values: Dict[str, Any],
  195. _fields_set: Union[Set[str], None] = None,
  196. ) -> _TSQLModel:
  197. # Copy from Pydantic's BaseModel.construct()
  198. # Ref: https://github.com/pydantic/pydantic/blob/v2.5.2/pydantic/main.py#L198
  199. # Modified to not include everything, only the model fields, and to
  200. # set relationships
  201. # SQLModel override to get class SQLAlchemy __dict__ attributes and
  202. # set them back in after creating the object
  203. # new_obj = cls.__new__(cls)
  204. cls = type(self_instance)
  205. old_dict = self_instance.__dict__.copy()
  206. # End SQLModel override
  207. fields_values: Dict[str, Any] = {}
  208. defaults: Dict[
  209. str, Any
  210. ] = {} # keeping this separate from `fields_values` helps us compute `_fields_set`
  211. for name, field in cls.model_fields.items():
  212. if field.alias and field.alias in values:
  213. fields_values[name] = values.pop(field.alias)
  214. elif name in values:
  215. fields_values[name] = values.pop(name)
  216. elif not field.is_required():
  217. defaults[name] = field.get_default(call_default_factory=True)
  218. if _fields_set is None:
  219. _fields_set = set(fields_values.keys())
  220. fields_values.update(defaults)
  221. _extra: Union[Dict[str, Any], None] = None
  222. if cls.model_config.get("extra") == "allow":
  223. _extra = {}
  224. for k, v in values.items():
  225. _extra[k] = v
  226. # SQLModel override, do not include everything, only the model fields
  227. # else:
  228. # fields_values.update(values)
  229. # End SQLModel override
  230. # SQLModel override
  231. # Do not set __dict__, instead use setattr to trigger SQLAlchemy
  232. # object.__setattr__(new_obj, "__dict__", fields_values)
  233. # instrumentation
  234. for key, value in {**old_dict, **fields_values}.items():
  235. setattr(self_instance, key, value)
  236. # End SQLModel override
  237. object.__setattr__(self_instance, "__pydantic_fields_set__", _fields_set)
  238. if not cls.__pydantic_root_model__:
  239. object.__setattr__(self_instance, "__pydantic_extra__", _extra)
  240. if cls.__pydantic_post_init__:
  241. self_instance.model_post_init(None)
  242. elif not cls.__pydantic_root_model__:
  243. # Note: if there are any private attributes, cls.__pydantic_post_init__ would exist
  244. # Since it doesn't, that means that `__pydantic_private__` should be set to None
  245. object.__setattr__(self_instance, "__pydantic_private__", None)
  246. # SQLModel override, set relationships
  247. # Get and set any relationship objects
  248. for key in self_instance.__sqlmodel_relationships__:
  249. value = values.get(key, Undefined)
  250. if value is not Undefined:
  251. setattr(self_instance, key, value)
  252. # End SQLModel override
  253. return self_instance
  254. def sqlmodel_validate(
  255. cls: Type[_TSQLModel],
  256. obj: Any,
  257. *,
  258. strict: Union[bool, None] = None,
  259. from_attributes: Union[bool, None] = None,
  260. context: Union[Dict[str, Any], None] = None,
  261. update: Union[Dict[str, Any], None] = None,
  262. ) -> _TSQLModel:
  263. if not is_table_model_class(cls):
  264. new_obj: _TSQLModel = cls.__new__(cls)
  265. else:
  266. # If table, create the new instance normally to make SQLAlchemy create
  267. # the _sa_instance_state attribute
  268. # The wrapper of this function should use with _partial_init()
  269. with partial_init():
  270. new_obj = cls()
  271. # SQLModel Override to get class SQLAlchemy __dict__ attributes and
  272. # set them back in after creating the object
  273. old_dict = new_obj.__dict__.copy()
  274. use_obj = obj
  275. if isinstance(obj, dict) and update:
  276. use_obj = {**obj, **update}
  277. elif update:
  278. use_obj = ObjectWithUpdateWrapper(obj=obj, update=update)
  279. cls.__pydantic_validator__.validate_python(
  280. use_obj,
  281. strict=strict,
  282. from_attributes=from_attributes,
  283. context=context,
  284. self_instance=new_obj,
  285. )
  286. # Capture fields set to restore it later
  287. fields_set = new_obj.__pydantic_fields_set__.copy()
  288. if not is_table_model_class(cls):
  289. # If not table, normal Pydantic code, set __dict__
  290. new_obj.__dict__ = {**old_dict, **new_obj.__dict__}
  291. else:
  292. # Do not set __dict__, instead use setattr to trigger SQLAlchemy
  293. # instrumentation
  294. for key, value in {**old_dict, **new_obj.__dict__}.items():
  295. setattr(new_obj, key, value)
  296. # Restore fields set
  297. object.__setattr__(new_obj, "__pydantic_fields_set__", fields_set)
  298. # Get and set any relationship objects
  299. if is_table_model_class(cls):
  300. for key in new_obj.__sqlmodel_relationships__:
  301. value = getattr(use_obj, key, Undefined)
  302. if value is not Undefined:
  303. setattr(new_obj, key, value)
  304. return new_obj
  305. def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:
  306. old_dict = self.__dict__.copy()
  307. if not is_table_model_class(self.__class__):
  308. self.__pydantic_validator__.validate_python(
  309. data,
  310. self_instance=self,
  311. )
  312. else:
  313. sqlmodel_table_construct(
  314. self_instance=self,
  315. values=data,
  316. )
  317. object.__setattr__(
  318. self,
  319. "__dict__",
  320. {**old_dict, **self.__dict__},
  321. )
  322. else:
  323. from pydantic import BaseConfig as BaseConfig # type: ignore[assignment]
  324. from pydantic.errors import ConfigError
  325. from pydantic.fields import ( # type: ignore[attr-defined, no-redef]
  326. SHAPE_SINGLETON,
  327. ModelField,
  328. )
  329. from pydantic.fields import ( # type: ignore[attr-defined, no-redef]
  330. Undefined as Undefined, # noqa
  331. )
  332. from pydantic.fields import ( # type: ignore[attr-defined, no-redef]
  333. UndefinedType as UndefinedType,
  334. )
  335. from pydantic.main import ( # type: ignore[no-redef]
  336. ModelMetaclass as ModelMetaclass,
  337. )
  338. from pydantic.main import validate_model
  339. from pydantic.typing import resolve_annotations
  340. from pydantic.utils import ROOT_KEY, ValueItems
  341. from pydantic.utils import ( # type: ignore[no-redef]
  342. Representation as Representation,
  343. )
  344. class SQLModelConfig(BaseConfig): # type: ignore[no-redef]
  345. table: Optional[bool] = None # type: ignore[misc]
  346. registry: Optional[Any] = None # type: ignore[misc]
  347. def get_config_value(
  348. *, model: InstanceOrType["SQLModel"], parameter: str, default: Any = None
  349. ) -> Any:
  350. return getattr(model.__config__, parameter, default) # type: ignore[union-attr]
  351. def set_config_value(
  352. *,
  353. model: InstanceOrType["SQLModel"],
  354. parameter: str,
  355. value: Any,
  356. ) -> None:
  357. setattr(model.__config__, parameter, value) # type: ignore
  358. def get_model_fields(model: InstanceOrType[BaseModel]) -> Dict[str, "FieldInfo"]:
  359. return model.__fields__ # type: ignore
  360. def get_fields_set(
  361. object: InstanceOrType["SQLModel"],
  362. ) -> Union[Set[str], Callable[[BaseModel], Set[str]]]:
  363. return object.__fields_set__
  364. def init_pydantic_private_attrs(new_object: InstanceOrType["SQLModel"]) -> None:
  365. object.__setattr__(new_object, "__fields_set__", set())
  366. def get_annotations(class_dict: Dict[str, Any]) -> Dict[str, Any]:
  367. return resolve_annotations( # type: ignore[no-any-return]
  368. class_dict.get("__annotations__", {}),
  369. class_dict.get("__module__", None),
  370. )
  371. def is_table_model_class(cls: Type[Any]) -> bool:
  372. config = getattr(cls, "__config__", None)
  373. if config:
  374. return getattr(config, "table", False)
  375. return False
  376. def get_relationship_to(
  377. name: str,
  378. rel_info: "RelationshipInfo",
  379. annotation: Any,
  380. ) -> Any:
  381. temp_field = ModelField.infer( # type: ignore[attr-defined]
  382. name=name,
  383. value=rel_info,
  384. annotation=annotation,
  385. class_validators=None,
  386. config=SQLModelConfig,
  387. )
  388. relationship_to = temp_field.type_
  389. if isinstance(temp_field.type_, ForwardRef):
  390. relationship_to = temp_field.type_.__forward_arg__
  391. return relationship_to
  392. def is_field_noneable(field: "FieldInfo") -> bool:
  393. if not field.required: # type: ignore[attr-defined]
  394. # Taken from [Pydantic](https://github.com/samuelcolvin/pydantic/blob/v1.8.2/pydantic/fields.py#L946-L947)
  395. return field.allow_none and ( # type: ignore[attr-defined]
  396. field.shape != SHAPE_SINGLETON or not field.sub_fields # type: ignore[attr-defined]
  397. )
  398. return field.allow_none # type: ignore[no-any-return, attr-defined]
  399. def get_sa_type_from_field(field: Any) -> Any:
  400. if isinstance(field.type_, type) and field.shape == SHAPE_SINGLETON:
  401. return field.type_
  402. raise ValueError(f"The field {field.name} has no matching SQLAlchemy type")
  403. def get_field_metadata(field: Any) -> Any:
  404. metadata = FakeMetadata()
  405. metadata.max_length = field.field_info.max_length
  406. metadata.max_digits = getattr(field.type_, "max_digits", None)
  407. metadata.decimal_places = getattr(field.type_, "decimal_places", None)
  408. return metadata
  409. def post_init_field_info(field_info: FieldInfo) -> None:
  410. field_info._validate() # type: ignore[attr-defined]
  411. def _calculate_keys(
  412. self: "SQLModel",
  413. include: Optional[Mapping[Union[int, str], Any]],
  414. exclude: Optional[Mapping[Union[int, str], Any]],
  415. exclude_unset: bool,
  416. update: Optional[Dict[str, Any]] = None,
  417. ) -> Optional[AbstractSet[str]]:
  418. if include is None and exclude is None and not exclude_unset:
  419. # Original in Pydantic:
  420. # return None
  421. # Updated to not return SQLAlchemy attributes
  422. # Do not include relationships as that would easily lead to infinite
  423. # recursion, or traversing the whole database
  424. return (
  425. self.__fields__.keys() # noqa
  426. ) # | self.__sqlmodel_relationships__.keys()
  427. keys: AbstractSet[str]
  428. if exclude_unset:
  429. keys = self.__fields_set__.copy() # noqa
  430. else:
  431. # Original in Pydantic:
  432. # keys = self.__dict__.keys()
  433. # Updated to not return SQLAlchemy attributes
  434. # Do not include relationships as that would easily lead to infinite
  435. # recursion, or traversing the whole database
  436. keys = (
  437. self.__fields__.keys() # noqa
  438. ) # | self.__sqlmodel_relationships__.keys()
  439. if include is not None:
  440. keys &= include.keys()
  441. if update:
  442. keys -= update.keys()
  443. if exclude:
  444. keys -= {k for k, v in exclude.items() if ValueItems.is_true(v)}
  445. return keys
  446. def sqlmodel_validate(
  447. cls: Type[_TSQLModel],
  448. obj: Any,
  449. *,
  450. strict: Union[bool, None] = None,
  451. from_attributes: Union[bool, None] = None,
  452. context: Union[Dict[str, Any], None] = None,
  453. update: Union[Dict[str, Any], None] = None,
  454. ) -> _TSQLModel:
  455. # This was SQLModel's original from_orm() for Pydantic v1
  456. # Duplicated from Pydantic
  457. if not cls.__config__.orm_mode: # type: ignore[attr-defined] # noqa
  458. raise ConfigError(
  459. "You must have the config attribute orm_mode=True to use from_orm"
  460. )
  461. if not isinstance(obj, Mapping):
  462. obj = (
  463. {ROOT_KEY: obj}
  464. if cls.__custom_root_type__ # type: ignore[attr-defined] # noqa
  465. else cls._decompose_class(obj) # type: ignore[attr-defined] # noqa
  466. )
  467. # SQLModel, support update dict
  468. if update is not None:
  469. obj = {**obj, **update}
  470. # End SQLModel support dict
  471. if not getattr(cls.__config__, "table", False): # noqa
  472. # If not table, normal Pydantic code
  473. m: _TSQLModel = cls.__new__(cls)
  474. else:
  475. # If table, create the new instance normally to make SQLAlchemy create
  476. # the _sa_instance_state attribute
  477. m = cls()
  478. values, fields_set, validation_error = validate_model(cls, obj)
  479. if validation_error:
  480. raise validation_error
  481. # Updated to trigger SQLAlchemy internal handling
  482. if not getattr(cls.__config__, "table", False): # noqa
  483. object.__setattr__(m, "__dict__", values)
  484. else:
  485. for key, value in values.items():
  486. setattr(m, key, value)
  487. # Continue with standard Pydantic logic
  488. object.__setattr__(m, "__fields_set__", fields_set)
  489. m._init_private_attributes() # type: ignore[attr-defined] # noqa
  490. return m
  491. def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:
  492. values, fields_set, validation_error = validate_model(self.__class__, data)
  493. # Only raise errors if not a SQLModel model
  494. if (
  495. not is_table_model_class(self.__class__) # noqa
  496. and validation_error
  497. ):
  498. raise validation_error
  499. if not is_table_model_class(self.__class__):
  500. object.__setattr__(self, "__dict__", values)
  501. else:
  502. # Do not set values as in Pydantic, pass them through setattr, so
  503. # SQLAlchemy can handle them
  504. for key, value in values.items():
  505. setattr(self, key, value)
  506. object.__setattr__(self, "__fields_set__", fields_set)
  507. non_pydantic_keys = data.keys() - values.keys()
  508. if is_table_model_class(self.__class__):
  509. for key in non_pydantic_keys:
  510. if key in self.__sqlmodel_relationships__:
  511. setattr(self, key, data[key])