В дополнение к великому 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 полезных атрибута:
- У него есть файловый интерфейс, который может быть
Читать ()
, возвращая байты урл
Статус
Возвращает HTTP-код состоянияЗаголовки
Возвращает 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”