Small wrapper around a fp object which will tee everything read into a buffer, and when that file is closed it will execute a callback with the contents of that buffer. All attributes are proxied to the underlying file object. This class uses members with a double underscore (
| 13 | |
| 14 | |
| 15 | class CallbackFileWrapper: |
| 16 | """ |
| 17 | Small wrapper around a fp object which will tee everything read into a |
| 18 | buffer, and when that file is closed it will execute a callback with the |
| 19 | contents of that buffer. |
| 20 | |
| 21 | All attributes are proxied to the underlying file object. |
| 22 | |
| 23 | This class uses members with a double underscore (__) leading prefix so as |
| 24 | not to accidentally shadow an attribute. |
| 25 | |
| 26 | The data is stored in a temporary file until it is all available. As long |
| 27 | as the temporary files directory is disk-based (sometimes it's a |
| 28 | memory-backed-``tmpfs`` on Linux), data will be unloaded to disk if memory |
| 29 | pressure is high. For small files the disk usually won't be used at all, |
| 30 | it'll all be in the filesystem memory cache, so there should be no |
| 31 | performance impact. |
| 32 | """ |
| 33 | |
| 34 | def __init__( |
| 35 | self, fp: HTTPResponse, callback: Callable[[Buffer], None] | None |
| 36 | ) -> None: |
| 37 | self.__buf = NamedTemporaryFile("rb+", delete=True) |
| 38 | self.__fp = fp |
| 39 | self.__callback = callback |
| 40 | |
| 41 | def __getattr__(self, name: str) -> Any: |
| 42 | # The vagaries of garbage collection means that self.__fp is |
| 43 | # not always set. By using __getattribute__ and the private |
| 44 | # name[0] allows looking up the attribute value and raising an |
| 45 | # AttributeError when it doesn't exist. This stop things from |
| 46 | # infinitely recursing calls to getattr in the case where |
| 47 | # self.__fp hasn't been set. |
| 48 | # |
| 49 | # [0] https://docs.python.org/2/reference/expressions.html#atom-identifiers |
| 50 | fp = self.__getattribute__("_CallbackFileWrapper__fp") |
| 51 | return getattr(fp, name) |
| 52 | |
| 53 | def __is_fp_closed(self) -> bool: |
| 54 | try: |
| 55 | return self.__fp.fp is None |
| 56 | |
| 57 | except AttributeError: |
| 58 | pass |
| 59 | |
| 60 | try: |
| 61 | closed: bool = self.__fp.closed |
| 62 | return closed |
| 63 | |
| 64 | except AttributeError: |
| 65 | pass |
| 66 | |
| 67 | # We just don't cache it then. |
| 68 | # TODO: Add some logging here... |
| 69 | return False |
| 70 | |
| 71 | def _close(self) -> None: |
| 72 | result: Buffer |
no outgoing calls
searching dependent graphs…