configuration.py 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. # This file is part of Hypothesis, which may be found at
  2. # https://github.com/HypothesisWorks/hypothesis/
  3. #
  4. # Copyright the Hypothesis Authors.
  5. # Individual contributors are listed in AUTHORS.rst and the git log.
  6. #
  7. # This Source Code Form is subject to the terms of the Mozilla Public License,
  8. # v. 2.0. If a copy of the MPL was not distributed with this file, You can
  9. # obtain one at https://mozilla.org/MPL/2.0/.
  10. import os
  11. import warnings
  12. from pathlib import Path
  13. import _hypothesis_globals
  14. from hypothesis.errors import HypothesisSideeffectWarning
  15. __hypothesis_home_directory_default = Path.cwd() / ".hypothesis"
  16. __hypothesis_home_directory = None
  17. def set_hypothesis_home_dir(directory):
  18. global __hypothesis_home_directory
  19. __hypothesis_home_directory = None if directory is None else Path(directory)
  20. def storage_directory(*names, intent_to_write=True):
  21. if intent_to_write:
  22. check_sideeffect_during_initialization(
  23. "accessing storage for {}", "/".join(names)
  24. )
  25. global __hypothesis_home_directory
  26. if not __hypothesis_home_directory:
  27. if where := os.getenv("HYPOTHESIS_STORAGE_DIRECTORY"):
  28. __hypothesis_home_directory = Path(where)
  29. if not __hypothesis_home_directory:
  30. __hypothesis_home_directory = __hypothesis_home_directory_default
  31. return __hypothesis_home_directory.joinpath(*names)
  32. _first_postinit_what = None
  33. def check_sideeffect_during_initialization(
  34. what: str, *fmt_args: object, extra: str = ""
  35. ) -> None:
  36. """Called from locations that should not be executed during initialization, for example
  37. touching disk or materializing lazy/deferred strategies from plugins. If initialization
  38. is in progress, a warning is emitted.
  39. Note that computing the repr can take nontrivial time or memory, so we avoid doing so
  40. unless (and until) we're actually emitting the warning.
  41. """
  42. global _first_postinit_what
  43. # This is not a particularly hot path, but neither is it doing productive work, so we want to
  44. # minimize the cost by returning immediately. The drawback is that we require
  45. # notice_initialization_restarted() to be called if in_initialization changes away from zero.
  46. if _first_postinit_what is not None:
  47. return
  48. elif _hypothesis_globals.in_initialization:
  49. # Note: -Werror is insufficient under pytest, as doesn't take effect until
  50. # test session start.
  51. msg = what.format(*fmt_args)
  52. warnings.warn(
  53. f"Slow code in plugin: avoid {msg} at import time! Set PYTHONWARNINGS=error "
  54. "to get a traceback and show which plugin is responsible." + extra,
  55. HypothesisSideeffectWarning,
  56. stacklevel=3,
  57. )
  58. else:
  59. _first_postinit_what = (what, fmt_args)
  60. def notice_initialization_restarted(*, warn: bool = True) -> None:
  61. """Reset _first_postinit_what, so that we don't think we're in post-init. Additionally, if it
  62. was set that means that there has been a sideeffect that we haven't warned about, so do that
  63. now (the warning text will be correct, and we also hint that the stacktrace can be improved).
  64. """
  65. global _first_postinit_what
  66. if _first_postinit_what is not None:
  67. what, *fmt_args = _first_postinit_what
  68. _first_postinit_what = None
  69. if warn:
  70. check_sideeffect_during_initialization(
  71. what,
  72. *fmt_args,
  73. extra=" Additionally, set HYPOTHESIS_EXTEND_INITIALIZATION=1 to pinpoint the exact location.",
  74. )