--- /dev/null
+# 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)
--- /dev/null
+# 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()