From: Scott Worley Date: Tue, 19 Dec 2023 09:16:03 +0000 (-0800) Subject: Cleaner Fetcher interface X-Git-Url: http://git.scottworley.com/paperdoorknob/commitdiff_plain/705973e749e5a9da0453508dbd23de434a0fdc65?ds=sidebyside Cleaner Fetcher interface --- diff --git a/fetch.py b/fetch.py new file mode 100644 index 0000000..3c0c6a3 --- /dev/null +++ b/fetch.py @@ -0,0 +1,43 @@ +# paperdoorknob: Print glowfic +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, version 3. + + +from abc import ABC, abstractmethod +from contextlib import contextmanager +from typing import Iterator + +import requests +import requests_cache + + +class Fetcher(ABC): + @abstractmethod + def fetch(self, url: str) -> bytes: + raise NotImplementedError() + + +class _SessionFetcher(Fetcher): + + def __init__(self, session: requests.Session, timeout: int) -> None: + self._session = session + self._timeout = timeout + + def fetch(self, url: str) -> bytes: + with self._session.get(url, timeout=self._timeout) as r: + r.raise_for_status() + return r.content + + +@contextmanager +def DirectFetcher(timeout: int) -> Iterator[_SessionFetcher]: + with requests.session() as session: + yield _SessionFetcher(session, timeout) + + +@contextmanager +def CachingFetcher(cache_path: str, timeout: int) -> Iterator[_SessionFetcher]: + with requests_cache.CachedSession(cache_path, cache_control=True) as session: + yield _SessionFetcher(session, timeout) diff --git a/fetch_test.py b/fetch_test.py new file mode 100644 index 0000000..6bdf69e --- /dev/null +++ b/fetch_test.py @@ -0,0 +1,57 @@ +# paperdoorknob: Print glowfic +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, version 3. + + +import unittest +from requests import HTTPError +from testing.fakeserver import FakeGlowficServer +from fetch import CachingFetcher, DirectFetcher + +TIMEOUT = 8 + + +class TestFetch(unittest.TestCase): + def setUp(self) -> None: + self._server = self.enterContext(FakeGlowficServer()) + self._port = self._server.port() + + def testDirectFetch(self) -> None: + with DirectFetcher(TIMEOUT) as f: + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 1) + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 2) + + def testFetchCaching(self) -> None: + with CachingFetcher("testcache", TIMEOUT) as f: + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 1) + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 1) + + def testFetchPersistentCaching(self) -> None: + with CachingFetcher("testpersistentcache", TIMEOUT) as f: + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 1) + with CachingFetcher("testpersistentcache", TIMEOUT) as f: + f.fetch(f"http://localhost:{self._port}") + self.assertEqual(self._server.request_count(), 1) + + def testFetchErrors(self) -> None: + with DirectFetcher(TIMEOUT) as f: + with self.assertRaises(HTTPError): + f.fetch(f"http://localhost:{self._port}/not_found") + with self.assertRaises(HTTPError): + f.fetch(f"http://localhost:{self._port}/server_error") + with CachingFetcher("testerrorscache", TIMEOUT) as f: + with self.assertRaises(HTTPError): + f.fetch(f"http://localhost:{self._port}/not_found") + with self.assertRaises(HTTPError): + f.fetch(f"http://localhost:{self._port}/server_error") + + +if __name__ == '__main__': + unittest.main() diff --git a/setup.py b/setup.py index bd23bdd..27980a1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,10 @@ setup( author="Scott Worley", author_email="scottworley@scottworley.com", url="https://git.scottworley.com/paperdoorknob", - py_modules=['paperdoorknob'], + py_modules=[ + 'fetch', + 'paperdoorknob', + ], license="GPL-3.0", entry_points={'console_scripts': ['paperdoorknob = paperdoorknob:main']}, )