import abc
import pathlib

import httpx
from furl import furl

from linguee_api.utils import read_text

DEFAULT_EXAMPLES_DIRECTORY = pathlib.Path(__file__).parents[1] / "examples"


class DownloaderError(Exception):
    pass


class IDownloader(abc.ABC):
    @abc.abstractmethod
    async def download(self, url: str) -> str:
        """Download a page or raise an exception"""
        ...


class HTTPXDownloader(IDownloader):
    """
    Real downloader.

    Sends request to linguee.com to read the page.
    """

    async def download(self, url: str) -> str:
        async with httpx.AsyncClient() as client:
            try:
                response = await client.get(url)
            except httpx.ConnectError as e:
                raise DownloaderError(str(e)) from e
            if response.status_code != 200:
                raise DownloaderError(
                    f"The Linguee server returned {response.status_code}"
                )
            return response.text


class ExampleDownloader(IDownloader):
    """
    Fake downloader.

    Reads data from "examples" directory.
    """

    def __init__(self, examples_directory: pathlib.Path = DEFAULT_EXAMPLES_DIRECTORY):
        self.examples_directory = examples_directory
        if not self.examples_directory.is_dir():
            raise RuntimeError(f"Directory {self.examples_directory} not found")

    async def download(self, url: str) -> str:
        parsed_url = furl(url)
        query = parsed_url.args["query"]
        response_file = self.examples_directory / f"{query}.html"
        if not response_file.is_file():
            raise DownloaderError(f"Example for {query} not found")
        return read_text(response_file)

@@ -981,16 +981,488 @@
 .text%0A%0A%0A
+class MemoryCache(IDownloader):%0A    %22%22%22%0A    Memory cache.%0A%0A    Exposes the downloader interface, but requires the upstream to work and%0A    keeps records in memory.%0A    %22%22%22%0A%0A    def __init__(self, upstream: IDownloader):%0A        self.upstream = upstream%0A        self.cache: dict%5Bstr, str%5D = %7B%7D%0A%0A    async def download(self, url: str) -%3E str:%0A        if url not in self.cache:%0A            self.cache%5Burl%5D = await self.upstream.download(url)%0A        return self.cache%5Burl%5D%0A%0A%0A
 class Ex

Add MemoryCache