Конвертация из запросов в aiohttp - PullRequest
0 голосов
/ 20 июня 2020

В настоящее время у меня есть этот класс для выполнения запросов к API и кеширования ответа JSON:

import os
import pathlib
import json
import hashlib
import time

import requests


class NoJSONResponseError(Exception):
    pass


class JSONRequestCacher(object):
    """Manage a JSON object through the cache.

    Download the associated resource from the provided URL
    when need be and retrieve the JSON from a cached file
    if possible.
    """
    def __init__(self, duration=86400, cachedir=None):
        self.duration = duration
        self.cachedir = self._get_cachedir(cachedir)
        self._validate_cache()

    def _get_cachedir(self, cachedir):
        if cachedir is None:
            cachedir = os.environ.get(
                'CUSTOM_CACHEDIR',
                pathlib.Path(pathlib.Path.home(), '.custom_cache/')
            )
        return cachedir

    def _validate_cache(self):
        """Create the cache directory if it doesn't exist"""
        self.cachedir.mkdir(parents=True, exist_ok=True)

    def _request(self, url):
        """Perform the retrieval of the requested JSON data"""
        return requests.get(url)

    def save(self, raw, cachefile):
        """Save the provided raw JSON data into the cached file"""
        with open(cachefile, 'w') as out:
            json.dump(raw, out)

    def load(self, cachefile):
        """Retrieve the saved JSON data from the cached file"""
        with open(cachefile) as cached:
            return json.load(cached)

    def cache_is_valid(self, cachefile):
        """Check if cache exists and is more recent than the cutoff"""
        if cachefile.is_file():
            cache_age = time.time() - cachefile.stat().st_mtime
            return cache_age < self.duration
        return False

    def request(self, url, refresh=False):
        """The JSON data associated to the given URL.

        Either read from the cache or fetch from the web.
        """
        urlhash = hashlib.md5(url.encode()).hexdigest()
        cachefile = self.cachedir.joinpath(urlhash)
        start = time.time()
        if not refresh and self.cache_is_valid(cachefile):
            return self.load(cachefile), True, time.time() - start
        resp = self._request(url)
        resp.raise_for_status()
        try:
            raw = resp.json()
        except ValueError:
            raise NoJSONResponseError()
        self.save(raw, cachefile)
        return raw, False, resp.elapsed.total_seconds()

Затем у меня есть другие классы и код, которые вызывают метод request этого кода вот так :

class APIHelper():
    def __init__(self):
        self.cache = JSONRequestCacher()

    def fetch(val):
        url = 'my/url/{}'.format(val)
        return self.cache.request(url)

    def fetchall(vals):
        repsonses = []
        for val in vals:
            responses.append(self.fetch(val))
        return responses

Для небольшого количества vals это нормально, и на самом деле нет ничего страшного в том, чтобы подождать 10 минут. Однако сейчас я собираюсь сделать 30 000+ обращений к этой конечной точке. Раньше я использовал пулы потоков (multiprocessing.dummy.Pool) для достижения некоторого параллелизма, однако, по моему мнению, async/await и aiothttp - лучший способ для go. К сожалению, как бы я ни старался, я не могу понять, как перевести это в этот код. Я использую Python 3.8.

EDIT Я пробовал внести это изменение:

class JSONRequestCacher():
    def __init__():
        self.http = aiohttp.ClientSession()

    async def _request(self, url):
            async with self.http.get(url) as response:
                return await response.read()

Получил ошибку: AttributeError: 'coroutine' object has no attribute 'json' из моей raw = resp.json() строки

Пытался добавить resp = await self._request(url), но это SyntaxError: 'await' outside async function. Затем, если я создаю request асинхронную c функцию, то ее вызов просто возвращает мне объект сопрограммы, который не дает мне ожидаемого ответа.

И это просто пытается сделать _request вызов asyn c. Я даже не могу понять, как я должен делать несколько вызовов к нему через другой класс (APIHelper).

...