base.py 5.6 KB

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