compat.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. # mypy: no-warn-unused-ignores
  2. from __future__ import annotations
  3. from configparser import ConfigParser
  4. import io
  5. import os
  6. from pathlib import Path
  7. import sys
  8. import typing
  9. from typing import Any
  10. from typing import Iterator
  11. from typing import List
  12. from typing import Optional
  13. from typing import Sequence
  14. from typing import Union
  15. if True:
  16. # zimports hack for too-long names
  17. from sqlalchemy.util import ( # noqa: F401
  18. inspect_getfullargspec as inspect_getfullargspec,
  19. )
  20. from sqlalchemy.util.compat import ( # noqa: F401
  21. inspect_formatargspec as inspect_formatargspec,
  22. )
  23. is_posix = os.name == "posix"
  24. py314 = sys.version_info >= (3, 14)
  25. py313 = sys.version_info >= (3, 13)
  26. py312 = sys.version_info >= (3, 12)
  27. py311 = sys.version_info >= (3, 11)
  28. py310 = sys.version_info >= (3, 10)
  29. py39 = sys.version_info >= (3, 9)
  30. # produce a wrapper that allows encoded text to stream
  31. # into a given buffer, but doesn't close it.
  32. # not sure of a more idiomatic approach to this.
  33. class EncodedIO(io.TextIOWrapper):
  34. def close(self) -> None:
  35. pass
  36. if py39:
  37. from importlib import resources as _resources
  38. importlib_resources = _resources
  39. from importlib import metadata as _metadata
  40. importlib_metadata = _metadata
  41. from importlib.metadata import EntryPoint as EntryPoint
  42. else:
  43. import importlib_resources # type:ignore # noqa
  44. import importlib_metadata # type:ignore # noqa
  45. from importlib_metadata import EntryPoint # type:ignore # noqa
  46. if py311:
  47. import tomllib as tomllib
  48. else:
  49. import tomli as tomllib # type: ignore # noqa
  50. if py312:
  51. def path_walk(
  52. path: Path, *, top_down: bool = True
  53. ) -> Iterator[tuple[Path, list[str], list[str]]]:
  54. return Path.walk(path)
  55. def path_relative_to(
  56. path: Path, other: Path, *, walk_up: bool = False
  57. ) -> Path:
  58. return path.relative_to(other, walk_up=walk_up)
  59. else:
  60. def path_walk(
  61. path: Path, *, top_down: bool = True
  62. ) -> Iterator[tuple[Path, list[str], list[str]]]:
  63. for root, dirs, files in os.walk(path, topdown=top_down):
  64. yield Path(root), dirs, files
  65. def path_relative_to(
  66. path: Path, other: Path, *, walk_up: bool = False
  67. ) -> Path:
  68. """
  69. Calculate the relative path of 'path' with respect to 'other',
  70. optionally allowing 'path' to be outside the subtree of 'other'.
  71. OK I used AI for this, sorry
  72. """
  73. try:
  74. return path.relative_to(other)
  75. except ValueError:
  76. if walk_up:
  77. other_ancestors = list(other.parents) + [other]
  78. for ancestor in other_ancestors:
  79. try:
  80. return path.relative_to(ancestor)
  81. except ValueError:
  82. continue
  83. raise ValueError(
  84. f"{path} is not in the same subtree as {other}"
  85. )
  86. else:
  87. raise
  88. def importlib_metadata_get(group: str) -> Sequence[EntryPoint]:
  89. ep = importlib_metadata.entry_points()
  90. if hasattr(ep, "select"):
  91. return ep.select(group=group)
  92. else:
  93. return ep.get(group, ()) # type: ignore
  94. def formatannotation_fwdref(
  95. annotation: Any, base_module: Optional[Any] = None
  96. ) -> str:
  97. """vendored from python 3.7"""
  98. # copied over _formatannotation from sqlalchemy 2.0
  99. if isinstance(annotation, str):
  100. return annotation
  101. if getattr(annotation, "__module__", None) == "typing":
  102. return repr(annotation).replace("typing.", "").replace("~", "")
  103. if isinstance(annotation, type):
  104. if annotation.__module__ in ("builtins", base_module):
  105. return repr(annotation.__qualname__)
  106. return annotation.__module__ + "." + annotation.__qualname__
  107. elif isinstance(annotation, typing.TypeVar):
  108. return repr(annotation).replace("~", "")
  109. return repr(annotation).replace("~", "")
  110. def read_config_parser(
  111. file_config: ConfigParser,
  112. file_argument: Sequence[Union[str, os.PathLike[str]]],
  113. ) -> List[str]:
  114. if py310:
  115. return file_config.read(file_argument, encoding="locale")
  116. else:
  117. return file_config.read(file_argument)