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
11import threading
12from typing import Any, Callable
13
14
15class ThreadLocal:
16 """
17 Manages thread-local state. ThreadLocal forwards getattr and setattr to a
18 threading.local() instance. The passed kwargs defines the available attributes
19 on the threadlocal and their default values.
20
21 The only supported names to geattr and setattr are the keys of the passed kwargs.
22 """
23
24 def __init__(self, **kwargs: Callable) -> None:
25 for name, value in kwargs.items():
26 if not callable(value):
27 raise TypeError(f"Attribute {name} must be a callable. Got {value}")
28
29 self.__initialized = False
30 self.__kwargs = kwargs
31 self.__threadlocal = threading.local()
32 self.__initialized = True
33
34 def __getattr__(self, name: str) -> Any:
35 if name not in self.__kwargs:
36 raise AttributeError(f"No attribute {name}")
37
38 if not hasattr(self.__threadlocal, name):
39 default = self.__kwargs[name]()
40 setattr(self.__threadlocal, name, default)
41
42 return getattr(self.__threadlocal, name)
43
44 def __setattr__(self, name: str, value: Any) -> None:
45 # disable attribute-forwarding while initializing
46 if "_ThreadLocal__initialized" not in self.__dict__ or not self.__initialized:
47 super().__setattr__(name, value)
48 else:
49 if name not in self.__kwargs:
50 raise AttributeError(f"No attribute {name}")
51 setattr(self.__threadlocal, name, value)