Как я могу объединить несколько адаптеров в запросах Python?

Мне нужно сделать несколько вызовов к конечной веб-точке, что несколько ненадежно, поэтому я разработал стратегию тайм-аута/повторной попытки, которую я передаю объекту requests.Session() в качестве адаптера.

Однако мне также необходимо смонтировать эту же конечную точку, используя клиентский сертификат PKCS12 и общедоступный центр сертификации (проверить), что я могу легко выполнить с помощью пакета requests_pkcs12 — это также создает адаптер.

Итак, адаптер 1 выглядит следующим образом (собран из того, что я нашел в Интернете):

# timeout.py

from requests.adapters import HTTPAdapter

DEFAULT_TIMEOUT = 30

class TimeoutAdapter(HTTPAdapter):
    def __init__(self, *args, **kwargs):
        self.timeout = DEFAULT_TIMEOUT
        if "timeout" in kwargs:
            self.timeout = kwargs["timeout"]
        super().__init__(*args, **kwargs)
    
    def send(self, request, **kwargs):
        timeout = kwargs.get("timeout")
        if timeout is None:
            kwargs["timeout"] = self.timeout
        return super().send(request, **kwargs)

## set up adapter
if __name__ == "__main__":
    
    from urllib3.util.retry import Retry
    
    u = 'https://some-webservice.com'
    s = requests.Session()
    retry_strategy = Retry(
        total=10,
        status_forcelist=[429, 500, 502, 503, 504],
        backoff_factor=1)
    s.mount(u, adapter=TimeoutAdapter(max_retries=retry_strategy)

Тогда второй адаптер выглядит так:

from requests import Session
from requests_pkcs12 import Pkcs12Adapter

pki_adapter = Pkcs12Adapter(
    pkcs12_filename='path/to/cert.p12',
    pkcs12_password='cert-password')

u = 'https://some-webservice.com'
s = requests.Session()
s.mount(u, adapter=pki_adapter)

Оба прекрасно работают сами по себе, но как мне объединить их в один адаптер, чтобы сеанс использовал PKI для аутентификации, но также соблюдал стратегию тайм-аута/повторной попытки?

Отвечает ли это на ваш вопрос? Запросы Python: могу ли я использовать s.mount() последовательно?

bfontaine 21.03.2024 16:20

Я не могу проверить это в данный момент, но поскольку requests_pkcs12.Pkcs12Adapter наследует от requests.adapters.HTTPAdapter, я смогу просто настроить retry_strategy как обычно и сделать: s.mount(u, adapter=Pkcs12Adapter(cert, pw, verify, max_retries=retry_strategy), верно?

auslander 21.03.2024 16:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
207
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Думали ли вы сделать что-то подобное?

from requests.adapters import HTTPAdapter
from requests_pkcs12 import Pkcs12Adapter
from urllib3.util.retry import Retry
import requests
import logging

DEFAULT_TIMEOUT = 30

class TimeoutPkcs12Adapter(HTTPAdapter):
    def __init__(self, pkcs12_filename, pkcs12_password, timeout=DEFAULT_TIMEOUT, **kwargs):
        self.timeout = timeout
        self.pkcs12_adapter = Pkcs12Adapter(pkcs12_filename=pkcs12_filename, pkcs12_password=pkcs12_password)
        super().__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
        # Load certificate details into pool manager
        pool_kwargs['cert_file'] = self.pkcs12_adapter.cert_file
        pool_kwargs['key_file'] = self.pkcs12_adapter.key_file
        super().init_poolmanager(connections, maxsize, block, **pool_kwargs)

    def send(self, request, **kwargs):
        kwargs.setdefault('timeout', self.timeout)
        try:
            return super().send(request, **kwargs)
        except requests.exceptions.RequestException as e:
            logging.error(f"Request failed: {e}")
            raise

if __name__ == "__main__":
    url = 'https://some-webservice.com'
    session = requests.Session()
    retry_strategy = Retry(total=10, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=1)
    adapter = TimeoutPkcs12Adapter(
        pkcs12_filename='path/to/cert.p12',
        pkcs12_password='cert-password',
        max_retries=retry_strategy
    )
    session.mount(url, adapter)

это поможет вам объединиться с адаптерами по наследству.

Обновление: в предыдущем коде была проблема с Diamond.

Спасибо! Но вы имели в виду class TimeoutPkcs12Adapter(HTTPAdapter, Pkcs12Adapter):?

auslander 21.03.2024 16:34

Да, извините за ошибку, он должен наследовать от HTTPAdapter

prhmma 21.03.2024 16:44

К сожалению, это дает мне Cannot create consistent method ordering в классе. наследование — это сложно

auslander 21.03.2024 16:49

попробовал локально, и это сработало, в предыдущем ответе была проблема с алмазами.

prhmma 21.03.2024 17:00

Еще раз спасибо. Что в данном случае представляет собой файл ключа? Пакеты PKCS12 расшифровываются в памяти и не имеют файлов-ключей, так что, думаю, можно было бы просто пропустить это? У меня есть файл CA (.crt), на который Session().verify будет указывать, но для клиентов все находится в одном файле .p12.

auslander 21.03.2024 17:08

Я не уверен, что вы сможете сделать это правильно без key_file, но, пожалуйста, попробуйте и дайте мне знать, я тоже могу чему-то научиться из этого, спасибо <3

prhmma 21.03.2024 22:14

В итоге я отметил ответ @booboo, хотя он был очень близок к вашему, порядок может быть полезен и вам. Я очень благодарен вам за ваше время и ответы, они все равно были полезны!

auslander 22.03.2024 21:11
Ответ принят как подходящий

Предполагая, что вам нужно поведение тайм-аута по умолчанию от TimeoutAdapter, тогда, поскольку TimeoutAdapter и Pkcs12Adapter оба наследуют от HTTPAdapter, вы можете наследовать первое от второго (одиночное наследование). Например:

# timeout.py

from requests import Session
from requests_pkcs12 import Pkcs12Adapter

DEFAULT_TIMEOUT = 30

class TimeoutAdapter(Pkcs12Adapter):
    def __init__(self, *args, **kwargs):
        self.timeout = DEFAULT_TIMEOUT
        if "timeout" in kwargs:
            self.timeout = kwargs["timeout"]
        super().__init__(*args, **kwargs)
    
    def send(self, request, **kwargs):
        timeout = kwargs.get("timeout")
        if timeout is None:
            kwargs["timeout"] = self.timeout
        return super().send(request, **kwargs)

## set up adapter
if __name__ == "__main__":
    
    from urllib3.util.retry import Retry
    
    retry_strategy = Retry(
        total=10,
        status_forcelist=[429, 500, 502, 503, 504],
        backoff_factor=1)

    timeout_adapter = TimeoutAdapter(
        max_retries=retry_strategy,
        pkcs12_filename='path/to/cert.p12',
        pkcs12_password='cert-password'
    )

    u = 'https://some-webservice.com'
    s = requests.Session()
    s.mount(u, adapter=timeout_adapter)

Именно то, что я ищу. Дает возможность предлагать его в качестве дополнительного класса соединения, если в определенных случаях это не требуется. Спасибо!

auslander 22.03.2024 21:10

Другие вопросы по теме