base.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. """Base classes and other customizations for generated pydantic types."""
  2. # Older-style type annotations required for Pydantic v1 / python 3.8 compatibility.
  3. # ruff: noqa: UP006
  4. from __future__ import annotations
  5. from abc import ABC
  6. from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Literal, overload
  7. from pydantic import BaseModel, ConfigDict
  8. from typing_extensions import TypedDict, Unpack, override
  9. from .v1_compat import PydanticCompatMixin, to_camel
  10. if TYPE_CHECKING:
  11. from pydantic.main import IncEx
  12. class ModelDumpKwargs(TypedDict, total=False):
  13. """Shared keyword arguments for `BaseModel.model_{dump,dump_json}`.
  14. Newer pydantic versions may accept more arguments than are listed here.
  15. Last updated for pydantic v2.12.0.
  16. """
  17. include: IncEx | None
  18. exclude: IncEx | None
  19. context: Any | None
  20. by_alias: bool | None
  21. exclude_unset: bool
  22. exclude_defaults: bool
  23. exclude_none: bool
  24. exclude_computed_fields: bool
  25. round_trip: bool
  26. warnings: bool | Literal["none", "warn", "error"]
  27. fallback: Callable[[Any], Any] | None
  28. serialize_as_any: bool
  29. # ---------------------------------------------------------------------------
  30. # Base models and mixin classes.
  31. #
  32. # Extra info is provided for devs in inline comments, NOT docstrings. This
  33. # prevents it from showing up in generated docs for subclasses.
  34. # FOR INTERNAL USE ONLY: v1-compatible drop-in replacement for `pydantic.BaseModel`.
  35. # If pydantic v2 is detected, this is just `pydantic.BaseModel`.
  36. #
  37. # Deliberately inherits ALL default configuration from `pydantic.BaseModel`.
  38. class CompatBaseModel(PydanticCompatMixin, BaseModel):
  39. __doc__ = None # Prevent subclasses from inheriting the BaseModel docstring
  40. class JsonableModel(CompatBaseModel, ABC):
  41. # Base class with sensible defaults for converting to and from JSON.
  42. #
  43. # Automatically parse or serialize "raw" API data (e.g. convert to and from
  44. # camelCase keys):
  45. # - `.model_{dump,dump_json}()` should return JSON-ready dicts or JSON
  46. # strings.
  47. # - `.model_{validate,validate_json}()` should accept JSON-ready dicts or
  48. # JSON strings.
  49. #
  50. # Ensure round-trip serialization <-> deserialization between:
  51. # - `model_dump()` <-> `model_validate()`
  52. # - `model_dump_json()` <-> `model_validate_json()`
  53. #
  54. # These behaviors help models predictably handle GraphQL request or response
  55. # data.
  56. model_config = ConfigDict(
  57. # ---------------------------------------------------------------------------
  58. # Discouraged in v2.11+, deprecated in v3. Kept here for compatibility.
  59. populate_by_name=True,
  60. # ---------------------------------------------------------------------------
  61. # Introduced in v2.11, ignored in earlier versions
  62. validate_by_name=True,
  63. validate_by_alias=True,
  64. serialize_by_alias=True,
  65. # ---------------------------------------------------------------------------
  66. validate_assignment=True,
  67. use_attribute_docstrings=True,
  68. from_attributes=True,
  69. )
  70. # Custom default kwargs for `JsonableModel.model_{dump,dump_json}`:
  71. # - by_alias: Convert keys to JSON-ready names and objects to JSON-ready
  72. # dicts.
  73. # - round_trip: Ensure the result can round-trip.
  74. __DUMP_DEFAULTS: ClassVar[Dict[str, Any]] = dict(by_alias=True, round_trip=True)
  75. @overload # Actual signature
  76. def model_dump(
  77. self, *, mode: str, **kwargs: Unpack[ModelDumpKwargs]
  78. ) -> dict[str, Any]: ...
  79. @overload # In case pydantic adds more kwargs in future releases
  80. def model_dump(self, **kwargs: Any) -> dict[str, Any]: ...
  81. @override
  82. def model_dump(self, *, mode: str = "json", **kwargs: Any) -> dict[str, Any]:
  83. kwargs = {**self.__DUMP_DEFAULTS, **kwargs} # allows overrides, if needed
  84. return super().model_dump(mode=mode, **kwargs)
  85. @overload # Actual signature
  86. def model_dump_json(
  87. self, *, indent: int | None, **kwargs: Unpack[ModelDumpKwargs]
  88. ) -> str: ...
  89. @overload # In case pydantic adds more kwargs in future releases
  90. def model_dump_json(self, **kwargs: Any) -> str: ...
  91. @override
  92. def model_dump_json(self, *, indent: int | None = None, **kwargs: Any) -> str:
  93. kwargs = {**self.__DUMP_DEFAULTS, **kwargs} # allows overrides, if needed
  94. return super().model_dump_json(indent=indent, **kwargs)
  95. # Base class for all GraphQL-derived types.
  96. class GQLBase(JsonableModel, ABC):
  97. model_config = ConfigDict(
  98. validate_default=True,
  99. revalidate_instances="always",
  100. protected_namespaces=(), # Some GraphQL fields may begin with "model_"
  101. )
  102. # Base class for GraphQL result types, i.e. parsed GraphQL response data.
  103. class GQLResult(GQLBase, ABC):
  104. model_config = ConfigDict(
  105. alias_generator=to_camel, # Assume JSON names are camelCase, by default
  106. frozen=True, # Keep the actual response data immutable
  107. )
  108. # Base class for GraphQL input types, i.e. prepared variables or input objects
  109. # for queries and mutations.
  110. class GQLInput(GQLBase, ABC):
  111. # For GraphQL inputs, exclude null values when preparing JSON-able request
  112. # data.
  113. __DUMP_DEFAULTS: ClassVar[Dict[str, Any]] = dict(exclude_none=True)
  114. @override
  115. def model_dump(self, *, mode: str = "json", **kwargs: Any) -> dict[str, Any]:
  116. kwargs = {**self.__DUMP_DEFAULTS, **kwargs}
  117. return super().model_dump(mode=mode, **kwargs)
  118. @override
  119. def model_dump_json(self, *, indent: int | None = None, **kwargs: Any) -> str:
  120. kwargs = {**self.__DUMP_DEFAULTS, **kwargs}
  121. return super().model_dump_json(indent=indent, **kwargs)