Первоначально авторство По Shrikant Шарат
Если вы еще не проверили @ kennethreitz отличного питона-запросы библиотеки пока я предлагаю вам пойти и сделать это немедленно. Давай, я буду ждать тебя.
У тебя была конфета? Это один из самых красивых фрагментов кода на Python, который я читал. И это отличная библиотека с очень гуманным API.
Недавно я использовал эту библиотеку для нескольких внутренних проектов моей компании, и в какой-то момент мне нужно было сериализовать и сохранить объекты Session для дальнейшего использования. Это было не так просто, как я думал, поэтому делюсь своим опытом здесь.
Прежде всего, давайте создадим простой http-сервер, с которым мы будем связываться при помощи python-запросов. Сервер должен иметь возможность обрабатывать сеансы на основе файлов cookie, а также иметь базовую аутентификацию, поскольку эти вещи обрабатываются объектами Session запросов Python на стороне клиента. Я не буду обсуждать здесь код для сервера, вы можете получить его из bitbucket .
Когда сервер запущен, теперь для клиента, давайте делать запросы!
import requests as req URL_ROOT = 'http://localhost:5050' def get_logged_in_session(name): session = req.session(auth=('user', 'pass')) login_response = session.post(URL_ROOT + '/login', data={'name': name}) login_response.raise_for_status() return session def get_whoami(session): response = session.get(URL_ROOT + '/whoami') response.raise_for_status() return response.text
Я определил две функции здесь. Get_logged_in_session создаст новый сеанс и войдет на http-сервер и вернет этот сеанс. Любые последующие запросы с использованием этого сеанса будут выполняться так, как если бы вы вошли в систему. Это то, что будет проверено с помощью функции get_whoami, которая будет просто возвращать ответ от / whoami.
Давайте проверим это. Убедитесь, что server.py запущен и находится в другом терминале,
$ python -i client.py >>> s = get_logged_in_session('sharat') >>> get_whoami(s) u'You are sharat' >>> get_whoami(req.session(auth=('user', 'pass'))) u'You are a guest'
Работает отлично. Если мы передаем ему зарегистрированный сеанс, он дает нам имя пользователя, а если мы передаем ему новый сеанс, он дает нам гостя.
Теперь давайте предположим, что у нас есть две функции: serialize_session и deserialize_session, которые делают именно то, что говорят их имена. Мы можем проверить их, запустив небольшой test.py, как
from client import get_logged_in_session, get_whoami from serializer import deserialize_session, serialize_session session = get_logged_in_session('sharat') dsession = deserialize_session(serialize_session(session)) assert get_whoami(session) == get_whoami(dsession) print 'Success'
и фиктивный serializer.py
def serialize_session(session): return session def deserialize_session(session): return session
И с этим, конечно, тест не провалится
$ python test.py Success
Сериализация
Теперь для реализации функций в serializer.py. Простым было бы использовать рассол. Давай попробуем
import pickle as pk def serialize_session(session): return pk.dumps(session) def deserialize_session(data): return pk.loads(data)
Если вы запустите test.py сейчас, Python будет кричать на вас.
$ python test.py Traceback (most recent call last): File "test.py", line 10, in <module> dsession = deserialize_session(serialize_session(session)) [ ... ] raise TypeError, "can't pickle %s objects" % base.__name__ TypeError: can't pickle lock objects
Ну что ж, стоило попробовать, я полагаю.
Обновление : класс Session может быть создан для реализации протокола pickle, если вы хотите использовать pickle.
Следующим планом, который у меня был, было собрать атрибуты и данные из объекта Session, и этого было достаточно, чтобы воссоздать этот объект с помощью конструктора Session и сериализовать эти атрибуты как json. В конце концов, API-интерфейс Session очень прост в использовании, насколько сложно выбрать атрибуты из него? ?
Итак, я покопался в модуле sessions.py библиотеки python-запросов. А вот как выглядит подпись конструктора для объектов Session
def __init__(self, headers=None, cookies=None, auth=None, timeout=None, proxies=None, hooks=None, params=None, config=None, verify=True): # ...
Поэтому, если я выберу только эти значения, я смогу воссоздать объект сеанса. Милая.
import json import requests as req def serialize_session(session): attrs = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'config', 'verify'] session_data = {} for attr in attrs: session_data[attr] = getattr(session, attr) return json.dumps(session_data) def deserialize_session(data): return req.session(**json.loads(data))
И давайте попробуем это
$ python test.py Traceback (most recent call last): File "test.py", line 12, in <module> assert get_whoami(session) == get_whoami(dsession) [ ... ] [...]requests/models.py", line 447, in send r = self.auth(self) TypeError: 'list' object is not callable
Хорошо, это сообщение об ошибке очень странно. Зачем кому-то называть объект списка?
Идите покопаться в модуле models.py . Посмотри это
[ ... ] if isinstance(self.auth, tuple) and len(self.auth) == 2: # special-case basic HTTP auth self.auth = HTTPBasicAuth(*self.auth) # Allow auth to make its changes. r = self.auth(self) [ ... ]
Там. Это не список, который называется. По крайней мере, не напрямую. Проблема здесь в том, что auth, который мы передаем для session (), не является кортежем. Duh! Хотя мне нравится, что auth ограничен быть кортежем, хотелось бы, чтобы было лучше сообщение об ошибке, когда auth является списком, а не кортежем. Я лично не хотел бы, чтобы это принимало список для аутентификации все же.
Итак, что пошло не так? JSON не различает кортеж и список. Это только списки. Таким образом, при сериализации и десериализации кортеж аутентификации превращается в список. Давайте вернемся назад
def deserialize_session(data): session_data = json.loads(data) if 'auth' in session_data: session_data['auth'] = tuple(session_data['auth']) return req.session(**session_data)
И
$ python test.py Traceback (most recent call last): File "test.py", line 12, in <module> assert get_whoami(session) == get_whoami(dsession) [ ... ] File "/usr/lib/python2.7/string.py", line 493, in translate return s.translate(table, deletions) TypeError: translate() takes exactly one argument (2 given)
Подождите. Какая? Теперь у нас есть ошибка от stdlib? Это становится все лучше и лучше. Если это похоже на то, что может вас расстроить, принесите кофе ?
Если вы посмотрите на полную трассировку стека, второй файл снизу,
File "[...]site-packages/requests/packages/oreos/monkeys.py", line 470, in set if "" != translate(key, idmap, LegalChars):
Кажется, эта штука неправильно вызывает метод translate. После небольшой отладки и криков на мониторе я обнаружил проблему и на мгновение потерял контроль над реальностью.
str.translate принимает 2 аргумента, но unicode.translate принимает только 1. Я понятия не имею, почему это делается таким образом, но я уверен, что, черт возьми, это не понравилось. Код в oreos / monkeys.py предполагает, что ключ является str. Однако то, что дает вам json.loads, — это юникод. Итак, нам нужно преобразовать только части в десериализованном dict, который мы получаем из json.loads, который используется в oreos / monkeys.py, из unicode в str.
Прочитав немного кода вокруг библиотеки oreos, не потребовалось много времени, чтобы выяснить, что это были ключи в файле cookie. вот
def deserialize_session(data): session_data = json.loads(data) if 'auth' in session_data: session_data['auth'] = tuple(session_data['auth']) if 'cookies' in session_data: session_data['cookies'] = dict((key.encode(), val) for key, val in session_data['cookies'].items()) return req.session(**session_data)
И так
$ python test.py Success
!
Весь код находится в репозитории bitbucket .
Обновление: травление также может работать
Как отметил Дасльх в своем комментарии к Reddit, реализуя протокол Pickle в классе Session, мы можем заставить работать Pickle. Из документации нам нужны два метода, __getstate__ и __setstate__.
Добавление этих методов следующим образом в класс sessions.Session
def __getstate__(self): attrs = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'config', 'verify'] return dict((attr, getattr(self, attr)) for attr in attrs) def __setstate__(self, state): for name, value in state.items(): setattr(self, name, value) self.poolmanager = PoolManager( num_pools=self.config.get('pool_connections'), maxsize=self.config.get('pool_maxsize') )
с этой версией serializer.py, использующей pickle, мы получаем успех.
Создание нового пула-менеджера в __setstate__ — это фрагмент кода, скопированный из __init__ того же класса. Вероятно, следует обратиться к методу, чтобы избежать повторения кода.
Обновление 2 : создал проблему по этому поводу.
Обновление 3 : это было объединено, и объекты Session могут быть выбраны с версии 0.10.3. Смотрите историю запросов .
Источник: http://sharats.me/serializing-python-requests-session-objects-for-fun-and-profit.html