Рубрики
Без рубрики

HTTP вызовы в Python без запросов или других внешних зависимостей

В дополнение к Great Python HTTP-клиентских инструментов, таких как запросы и httpx, сама стандартная библиотека … помечена Python, Tutorial, WebDev, программированием.

В дополнение к великому Python HTTP клиентские инструменты, такие как Запросы и Httpx. Сама стандартная библиотека поставляет необходимые ингредиенты для создания рабочего клиента HTTP для вызовов API. В этом руководстве построится как построить и настроить такой инструмент для ваших собственных сценариев.

Рассмотрите возможность установки библиотеки

Прежде чем продолжить, я должен отметить, что во многих случаях подход в этой статье не является лучшей практикой. Вместо этого я настоятельно рекомендую использовать стороннюю библиотеку Python для функций, безопасности и надежности.

Некоторые предлагаемые библиотеки:

  • Urllib3 Это зависимость для многих других инструментов, в том числе Запросы Отказ Само по себе Urllib3 вполне возможно. Это может быть все, что вам нужно.
  • Запросы повсеместно и хорошо документирован.
  • Httpx имеет интерфейс практически идентичен Запросы , но с дополнительным преимуществом асинсио служба поддержки. Вы можете быть заинтересованы в Серия статей, которые я написал на использовании httpx как синхронно, так и асинхронно.
  • Pycurl менее популярен как библиотека Python, но интерфейсы с известными libcurl Отказ
  • AioHTTP У Asyncio Http-клиент, который хорошо документирован и любимый.

Если, однако, вы оказываетесь, что нужно, чтобы решение, которое не требует внешних зависимостей, кроме то, что уже доступно в стандартной библиотеке Python, тогда вы можете пожелать прочитать.

Сводный код

import json
import typing
import urllib.error
import urllib.parse
import urllib.request
from email.message import Message


class Response(typing.NamedTuple):
    body: str
    headers: Message
    status: int
    error_count: int = 0

    def json(self) -> typing.Any:
        """
        Decode body's JSON.

        Returns:
            Pythonic representation of the JSON object
        """
        try:
            output = json.loads(self.body)
        except json.JSONDecodeError:
            output = ""
        return output


def request(
    url: str,
    data: dict = None,
    params: dict = None,
    headers: dict = None,
    method: str = "GET",
    data_as_json: bool = True,
    error_count: int = 0,
) -> Response:
    if not url.casefold().startswith("http"):
        raise urllib.error.URLError("Incorrect and possibly insecure protocol in url")
    method = method.upper()
    request_data = None
    headers = headers or {}
    data = data or {}
    params = params or {}
    headers = {"Accept": "application/json", **headers}

    if method == "GET":
        params = {**params, **data}
        data = None

    if params:
        url += "?" + urllib.parse.urlencode(params, doseq=True, safe="/")

    if data:
        if data_as_json:
            request_data = json.dumps(data).encode()
            headers["Content-Type"] = "application/json; charset=UTF-8"
        else:
            request_data = urllib.parse.urlencode(data).encode()

    httprequest = urllib.request.Request(
        url, data=request_data, headers=headers, method=method
    )

    try:
        with urllib.request.urlopen(httprequest) as httpresponse:
            response = Response(
                headers=httpresponse.headers,
                status=httpresponse.status,
                body=httpresponse.read().decode(
                    httpresponse.headers.get_content_charset("utf-8")
                ),
            )
    except urllib.error.HTTPError as e:
        response = Response(
            body=str(e.reason),
            headers=e.headers,
            status=e.code,
            error_count=error_count + 1,
        )

    return response

Вы обязательно будете получать копию и использовать вышеуказанную функцию или просматривать или клон Github Reppo Отказ

Если, однако, вы читаете эту статью для подхода к себе, я рекомендую вам создать собственную функцию, которая соответствует вашим потребностям. Это может расти проще или более гибко, чем выше.

Давайте обсудим строительные блоки.

Введение в Urllib.request.urlopen ()

Рекомендуемая функция высокого уровня для HTTP-запросов Urlopen () , доступно в стандарте Urllib.request модуль.

В отличие от нижнего уровня http.client Модуль, Urlopen () Обеспечивает обработку ошибок, следует перенаправить и обеспечивает удобство вокруг заголовков и данных.

Пример:

from urllib.request import urlopen, Request

url = "https://jsonplaceholder.typicode.com/posts/1"

if not url.startswith("http"):
    raise RuntimeError("Incorrect and possibly insecure protocol in url")

httprequest = Request(url, headers={"Accept": "application/json"})

with urlopen(httprequest) as response:
    print(response.status)
    print(response.read().decode())

Я высоко ценю и рекомендую JSONDOLLER Свободный поддельный API, используемый выше. Полезно именно для того, что мы здесь делаем: Тестирование клиентов HTTP, предназначенные для работы API.

Обратите внимание на меры безопасности в вышеуказанном коде. Прежде чем передавать URL для Urlopen () , Убедитесь, что это веб-URL-адрес, а не локальный файл (« файл:/// ») Отказ Если вы хотите звонить в пробуждение, попробуйте URLOPEN («Файл:///etc/passwd») .read () На системе Linux (не в производственном коде, хотя!) И посмотрите, что происходит. Конечно, эта проверка протокола необходима только в том случае, если URL поступает от ввода пользователя. Если вы управляете строкой URL и можете заверить, что он не запускается с « Файл: », то это хорошо. Вы также можете быть заинтересованы в другом подходе к Утверждение Urlopen () путем переопределения списка обработчиков протокола .

Протокол проверять в сторону, Urlopen () Вызов довольно прост, как вы можете видеть в приведенном выше примере. Я рекомендую использовать его рядом с с В контексте менеджера Для приливов, чтобы закрытие ответа обрабатывается автоматически.

Мы прошли Запрос объект к Urlopen () функция. Хотя мы могли бы просто передать строку URL, Запрос Объект предлагает гораздо больше гибкости: мы можем указать http Метод (Получить, пост, поставить, голову, удалить), Запрос Заголовки и запрос данные Отказ

ответ возвращено Urlopen имеет 4 полезных атрибута:

  1. У него есть файловый интерфейс, который может быть Читать () , возвращая байты
  2. урл
  3. Статус Возвращает HTTP-код состояния
  4. Заголовки Возвращает EmailMessage объект. Это функционирует несколько как Дикт Но с ключей нечувствительными к регистру. У него также есть некоторые полезные методы, такие как get_content_type () и get_content_charset () Отказ get_all () Метод – еще один полезный, для того, когда может быть несколько пар клавиш/значение для одного и того же заголовка. Посмотреть Полезная статья Википедии для списка возможных заголовков ответа.

HTTP-ошибки

Из коробки, Urlopen Ручки перенаправления (коды состояния 301, 302, 303 или 307). Кроме этих кодов, хотя, если код состояния не между 200 и 299 (HTTP «OK» коды в соответствии с RFC 2616 ) Тогда HTTPERROR Исключение поднимается.

HTTPERROR может быть захвачен и проанализирован с соответствующим попробуй ... кроме ... Блок, такой как это:

from urllib.error import HTTPError
from urllib.request import urlopen

try:
    urlopen("https://github.com/404")
except HTTPError as e:
    print(e.status)
    print(e.reason)
    print(e.headers.get_content_type())

Ошибка (присваиваемая переменной « e » в вышеупомянутой) имеет следующие полезные свойства:

  • Статус Чтобы получить код ошибки (например, 404)
  • Заголовки как EmailMessage объект. Опять же, это можно лечить как нечувствительность к регистру Дикт Отказ
  • Причина с текстом ошибки

В функции в верхней части этой статьи я поймаю и замолчаю все ошибки, чтобы сделать форму реагирования и пройти через ошибку ответственности вниз по течению. Однако это не может быть желательно. Возможно, вместо того, чтобы продолжить независимо от ошибки, вы хотите потерпеть неудачу на что-то кроме ошибки 401 или 429:

except urllib.error.HTTPError as e:
    if e.code in (401, 429):
        response = Response(
            body=str(e.reason),
            headers=e.headers,
            status=e.code,
            error_count=error_count + 1,
        )
    else:
        raise e

Конечно, логика может быть добавлена для решения ошибок в зависимости от обстоятельств в зависимости от кода состояния.

Обратите внимание на совершенно необязательное автоматическое увеличение error_count атрибут в моем коде. Иногда я хочу вызвать функцию HTTP-запроса рекурсивно. Это позволяет отслеживать количество вызовов и справиться с ними вниз по течению, надеюсь, предотвращая бесконечную рекурсию. Например, я могу захотеть уловить ошибки 401, разбирать заголовок «WWW-аутентификации» для токена, затем повторите запрос с токеном. Но если это не удается неоднократно (скажем, 5 пытается), он должен остановиться. Я мог бы проверить error_count и повысить и исключение, если так, тем временем обязательно пройти ток error_count Вернуться к функции запроса в качестве параметра, поэтому он продолжает соответственно увеличить.

Альтернативный способ настроить обработку ошибок состоит в том, чтобы построить свои собственные подклассы BaseHandler Тогда построить открывающийИректор цепь обработчиков в зависимости от обстоятельств. Например, вы можете подкласс BaseHandler и добавить метод http_error_401 Чтобы обрабатывать авторизацию по желанию, затем передайте экземпляр этого пользовательского класса на build_opener () Отказ Очевидно, что это требует глубокого погружения в открывшиеся внутренности.

Объект универсального ответа

Я считаю, что это полезно создать класс Python, который может содержать биты отклика HTTP, о котором я забочусь о. Это может быть Дикт , но мне нравится добавлять метод или два, например, декодер JSON.

При использовании Python 3.7 или позже рассмотреть возможность использования Dataclass Отказ Пример:

@dataclass(frozen=True)
class Response():
    body: str
    headers: Message
    status: int
    error_count: int = 0

    def json(self) -> typing.Any:
        """
        Decode body's JSON.

        Returns:
            Pythonic representation of the JSON object
        """
        try:
            output = json.loads(self.body)
        except json.JSONDecodeError:
            output = ""
        return output

Включение замороженные строго необязательно, и отражает мои предпочтения для того, чтобы этот объект был неизменным (атрибуты не могут быть изменены после инициализации).

Другой вариант, как продемонстрировано в коде в начале статьи, является Набрал namedtuple. . Я выбрал это для его неизменности, простотой настройки и обратной совместимости.

Конечно, пользовательский класс будет работать, или attrs или любой контейнер работает для вас.

Запросы с данными

В зависимости от API, с которым вы взаимодействуете, вы можете столкнуться с различными сценариями для принятия данных. В каждом сценарии мы можем начать с Python Дикт и преобразовать его в требуемый формат.

Строка запроса

Иногда данные передаются через строку запроса. Кодировать Python Дикт В качестве строки запроса, которую можно добавить к URL (после « ? »), используйте Urllib.parse.urlencode. :

from urllib.parse import urlencode
from urllib.request import urlopen

url = "https://jsonplaceholder.typicode.com/posts"
params = {"userId": 1, "_limit": 3}
url += "?" + urlencode(params, doseq=True, safe="/")

with urlopen(url) as response:
    print(response.read().decode())

Не имея отношения к вышеуказанному запросу, я прошел два параметра для Urlencode что я нашел полезным:

  • Дозек позволит кодировать списки как несколько параметров. Например, если мы прошли в {«Имена пользователей»: [«Джон Доу», «Джейн Доу»]} тогда конечный результат будет ” usernames = John + Doe & usernames = Джейн + Доу “.
  • безопасный Определяет символы, которые не будут закодированы URL. В некоторых API я столкнулся, например, Docker API, лучше оставить косой сомнение, поэтому я добавил это в безопасный нить. Адаптируйтесь, как вы видите в форме.

Отправка данных в тело запроса

Точно так же данные могут быть закодированы с Urllib.Parse.urlencode а затем передается в объект запроса через данные Параметр:

from urllib.parse import urlencode
from urllib.request import Request, urlopen

url = "https://api.funtranslations.com/translate/yoda.json"
data = {"text": "HTTP POST calls are remarkably easy"}

postdata = urlencode(data).encode()

httprequest = Request(url, data=postdata, method="POST")

with urlopen(httprequest) as response:
    print(response.read().decode())

Отправка JSON в тело запроса

Многие API принимают и даже требуют, чтобы параметры запроса будут отправлены как JSON. В этих случаях важно сначала кодировать Python Дикт (или другой объект) как JSON, затем установите « Content-Type» «Заголовок запроса запроса на запрос:

import json
from urllib.parse import urlencode
from urllib.request import Request, urlopen

url = "https://jsonplaceholder.typicode.com/posts"
data = {
    "userid": "1001",
    "title": "POSTing JSON for Fun and Profit",
    "body": "JSON in the request body! Don't forget the content type.",
}

postdata = json.dumps(data).encode()

headers = {"Content-Type": "application/json; charset=UTF-8"}

httprequest = Request(url, data=postdata, method="POST", headers=headers)

with urlopen(httprequest) as response:
    print(response.read().decode())

В приведенном выше мы использовали Встроенный модуль JSON Python бросить данные Дикт в строку, затем кодируйте его к байтам, чтобы затем он мог обрабатываться как данные пост.

Мы устанавливаем заголовок типа содержимого в Приложение/JSON Отказ Кроме того, мы указали кодировку символов как UTF-8. Учитывая, что UTF-8 это Требуемый кодировщик персонажа JSON , это избыточно и, вероятно, ненужный , Но никогда не болит быть явным.

Разбор JSON в телу ответа

Поскольку большинство API, я использую Return JSON, а некоторые возвращает другие форматы, такие как XML, если JSON не указан, я обычно устанавливаю Принимает Заголовок в запросе на Приложение/JSON. . Если вы тянете другие типы данных, такие как текст/CSV. Вы хотели бы настроить этот заголовок. Установите это на */* Если вам все равно.

В объекте ответа мы создали ранее, существует пример JSON Decoder, результатом которого, скорее всего, будет Python Дикт или Список , но может быть возможно быть строкой или логией, в зависимости от того, что возвращает сервер. Вот еще один пример аналогичной функциональности, но загрузка JSON непосредственно из файла ответ :

import json
from urllib.parse import urlencode
from urllib.request import Request, urlopen

url = "https://jsonplaceholder.typicode.com/posts?_limit=3"

with urlopen(url) as response:
    try:
        jsonbody = json.load(response)
    except json.JSONDecodeError:
        jsonbody = ""

print(jsonbody)

В приведенном выше примере я решил молча справиться, предлагая пустую строку, когда JSON Decoding не удается. Если это не желательно, просто используйте json.load () (или json.loads () из строки) и пусть какие-либо исключения плавают вверх, когда они возникают.

Другие трюки или предложения?

Мне очень любопытно, если вы используете урлопень и как. Есть ли оптимизация к вышесказанному, что мне не хватает? Это поднимает какие-либо вопросы или путаница? Не стесняйтесь публиковать в комментариях.

Оригинал: “https://dev.to/bowmanjd/http-calls-in-python-without-requests-or-other-external-dependencies-5aj1”