aliases.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """Support for alias configurations."""
  2. from __future__ import annotations
  3. import dataclasses
  4. from typing import Any, Callable, Literal
  5. from pydantic_core import PydanticUndefined
  6. from ._internal import _internal_dataclass
  7. __all__ = ('AliasGenerator', 'AliasPath', 'AliasChoices')
  8. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  9. class AliasPath:
  10. """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#aliaspath-and-aliaschoices
  11. A data class used by `validation_alias` as a convenience to create aliases.
  12. Attributes:
  13. path: A list of string or integer aliases.
  14. """
  15. path: list[int | str]
  16. def __init__(self, first_arg: str, *args: str | int) -> None:
  17. self.path = [first_arg] + list(args)
  18. def convert_to_aliases(self) -> list[str | int]:
  19. """Converts arguments to a list of string or integer aliases.
  20. Returns:
  21. The list of aliases.
  22. """
  23. return self.path
  24. def search_dict_for_path(self, d: dict) -> Any:
  25. """Searches a dictionary for the path specified by the alias.
  26. Returns:
  27. The value at the specified path, or `PydanticUndefined` if the path is not found.
  28. """
  29. v = d
  30. for k in self.path:
  31. if isinstance(v, str):
  32. # disallow indexing into a str, like for AliasPath('x', 0) and x='abc'
  33. return PydanticUndefined
  34. try:
  35. v = v[k]
  36. except (KeyError, IndexError, TypeError):
  37. return PydanticUndefined
  38. return v
  39. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  40. class AliasChoices:
  41. """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#aliaspath-and-aliaschoices
  42. A data class used by `validation_alias` as a convenience to create aliases.
  43. Attributes:
  44. choices: A list containing a string or `AliasPath`.
  45. """
  46. choices: list[str | AliasPath]
  47. def __init__(self, first_choice: str | AliasPath, *choices: str | AliasPath) -> None:
  48. self.choices = [first_choice] + list(choices)
  49. def convert_to_aliases(self) -> list[list[str | int]]:
  50. """Converts arguments to a list of lists containing string or integer aliases.
  51. Returns:
  52. The list of aliases.
  53. """
  54. aliases: list[list[str | int]] = []
  55. for c in self.choices:
  56. if isinstance(c, AliasPath):
  57. aliases.append(c.convert_to_aliases())
  58. else:
  59. aliases.append([c])
  60. return aliases
  61. @dataclasses.dataclass(**_internal_dataclass.slots_true)
  62. class AliasGenerator:
  63. """Usage docs: https://docs.pydantic.dev/2.10/concepts/alias#using-an-aliasgenerator
  64. A data class used by `alias_generator` as a convenience to create various aliases.
  65. Attributes:
  66. alias: A callable that takes a field name and returns an alias for it.
  67. validation_alias: A callable that takes a field name and returns a validation alias for it.
  68. serialization_alias: A callable that takes a field name and returns a serialization alias for it.
  69. """
  70. alias: Callable[[str], str] | None = None
  71. validation_alias: Callable[[str], str | AliasPath | AliasChoices] | None = None
  72. serialization_alias: Callable[[str], str] | None = None
  73. def _generate_alias(
  74. self,
  75. alias_kind: Literal['alias', 'validation_alias', 'serialization_alias'],
  76. allowed_types: tuple[type[str] | type[AliasPath] | type[AliasChoices], ...],
  77. field_name: str,
  78. ) -> str | AliasPath | AliasChoices | None:
  79. """Generate an alias of the specified kind. Returns None if the alias generator is None.
  80. Raises:
  81. TypeError: If the alias generator produces an invalid type.
  82. """
  83. alias = None
  84. if alias_generator := getattr(self, alias_kind):
  85. alias = alias_generator(field_name)
  86. if alias and not isinstance(alias, allowed_types):
  87. raise TypeError(
  88. f'Invalid `{alias_kind}` type. `{alias_kind}` generator must produce one of `{allowed_types}`'
  89. )
  90. return alias
  91. def generate_aliases(self, field_name: str) -> tuple[str | None, str | AliasPath | AliasChoices | None, str | None]:
  92. """Generate `alias`, `validation_alias`, and `serialization_alias` for a field.
  93. Returns:
  94. A tuple of three aliases - validation, alias, and serialization.
  95. """
  96. alias = self._generate_alias('alias', (str,), field_name)
  97. validation_alias = self._generate_alias('validation_alias', (str, AliasChoices, AliasPath), field_name)
  98. serialization_alias = self._generate_alias('serialization_alias', (str,), field_name)
  99. return alias, validation_alias, serialization_alias # type: ignore