contexts.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. from __future__ import annotations
  2. from contextlib import contextmanager
  3. import os
  4. from pathlib import Path
  5. import tempfile
  6. from typing import (
  7. IO,
  8. Any,
  9. Generator,
  10. )
  11. import uuid
  12. from pandas._typing import (
  13. BaseBuffer,
  14. CompressionOptions,
  15. FilePath,
  16. )
  17. from pandas.compat import PYPY
  18. from pandas.errors import ChainedAssignmentError
  19. from pandas import set_option
  20. from pandas.io.common import get_handle
  21. @contextmanager
  22. def decompress_file(
  23. path: FilePath | BaseBuffer, compression: CompressionOptions
  24. ) -> Generator[IO[bytes], None, None]:
  25. """
  26. Open a compressed file and return a file object.
  27. Parameters
  28. ----------
  29. path : str
  30. The path where the file is read from.
  31. compression : {'gzip', 'bz2', 'zip', 'xz', 'zstd', None}
  32. Name of the decompression to use
  33. Returns
  34. -------
  35. file object
  36. """
  37. with get_handle(path, "rb", compression=compression, is_text=False) as handle:
  38. yield handle.handle
  39. @contextmanager
  40. def set_timezone(tz: str) -> Generator[None, None, None]:
  41. """
  42. Context manager for temporarily setting a timezone.
  43. Parameters
  44. ----------
  45. tz : str
  46. A string representing a valid timezone.
  47. Examples
  48. --------
  49. >>> from datetime import datetime
  50. >>> from dateutil.tz import tzlocal
  51. >>> tzlocal().tzname(datetime(2021, 1, 1)) # doctest: +SKIP
  52. 'IST'
  53. >>> with set_timezone('US/Eastern'):
  54. ... tzlocal().tzname(datetime(2021, 1, 1))
  55. ...
  56. 'EST'
  57. """
  58. import time
  59. def setTZ(tz) -> None:
  60. if tz is None:
  61. try:
  62. del os.environ["TZ"]
  63. except KeyError:
  64. pass
  65. else:
  66. os.environ["TZ"] = tz
  67. time.tzset()
  68. orig_tz = os.environ.get("TZ")
  69. setTZ(tz)
  70. try:
  71. yield
  72. finally:
  73. setTZ(orig_tz)
  74. @contextmanager
  75. def ensure_clean(
  76. filename=None, return_filelike: bool = False, **kwargs: Any
  77. ) -> Generator[Any, None, None]:
  78. """
  79. Gets a temporary path and agrees to remove on close.
  80. This implementation does not use tempfile.mkstemp to avoid having a file handle.
  81. If the code using the returned path wants to delete the file itself, windows
  82. requires that no program has a file handle to it.
  83. Parameters
  84. ----------
  85. filename : str (optional)
  86. suffix of the created file.
  87. return_filelike : bool (default False)
  88. if True, returns a file-like which is *always* cleaned. Necessary for
  89. savefig and other functions which want to append extensions.
  90. **kwargs
  91. Additional keywords are passed to open().
  92. """
  93. folder = Path(tempfile.gettempdir())
  94. if filename is None:
  95. filename = ""
  96. filename = str(uuid.uuid4()) + filename
  97. path = folder / filename
  98. path.touch()
  99. handle_or_str: str | IO = str(path)
  100. if return_filelike:
  101. kwargs.setdefault("mode", "w+b")
  102. handle_or_str = open(path, **kwargs)
  103. try:
  104. yield handle_or_str
  105. finally:
  106. if not isinstance(handle_or_str, str):
  107. handle_or_str.close()
  108. if path.is_file():
  109. path.unlink()
  110. @contextmanager
  111. def ensure_safe_environment_variables() -> Generator[None, None, None]:
  112. """
  113. Get a context manager to safely set environment variables
  114. All changes will be undone on close, hence environment variables set
  115. within this contextmanager will neither persist nor change global state.
  116. """
  117. saved_environ = dict(os.environ)
  118. try:
  119. yield
  120. finally:
  121. os.environ.clear()
  122. os.environ.update(saved_environ)
  123. @contextmanager
  124. def with_csv_dialect(name, **kwargs) -> Generator[None, None, None]:
  125. """
  126. Context manager to temporarily register a CSV dialect for parsing CSV.
  127. Parameters
  128. ----------
  129. name : str
  130. The name of the dialect.
  131. kwargs : mapping
  132. The parameters for the dialect.
  133. Raises
  134. ------
  135. ValueError : the name of the dialect conflicts with a builtin one.
  136. See Also
  137. --------
  138. csv : Python's CSV library.
  139. """
  140. import csv
  141. _BUILTIN_DIALECTS = {"excel", "excel-tab", "unix"}
  142. if name in _BUILTIN_DIALECTS:
  143. raise ValueError("Cannot override builtin dialect.")
  144. csv.register_dialect(name, **kwargs)
  145. try:
  146. yield
  147. finally:
  148. csv.unregister_dialect(name)
  149. @contextmanager
  150. def use_numexpr(use, min_elements=None) -> Generator[None, None, None]:
  151. from pandas.core.computation import expressions as expr
  152. if min_elements is None:
  153. min_elements = expr._MIN_ELEMENTS
  154. olduse = expr.USE_NUMEXPR
  155. oldmin = expr._MIN_ELEMENTS
  156. set_option("compute.use_numexpr", use)
  157. expr._MIN_ELEMENTS = min_elements
  158. try:
  159. yield
  160. finally:
  161. expr._MIN_ELEMENTS = oldmin
  162. set_option("compute.use_numexpr", olduse)
  163. def raises_chained_assignment_error():
  164. if PYPY:
  165. from contextlib import nullcontext
  166. return nullcontext()
  167. else:
  168. from pandas._testing import assert_produces_warning
  169. return assert_produces_warning(
  170. ChainedAssignmentError,
  171. match=(
  172. "A value is trying to be set on a copy of a DataFrame or Series "
  173. "through chained assignment"
  174. ),
  175. )