httpie-cli/httpie/sessions.py

257 lines
7.4 KiB
Python
Raw Normal View History

"""Persistent, JSON-serialized sessions.
"""
import os
import sys
import json
import glob
import errno
import codecs
2012-08-21 15:45:22 +02:00
import shutil
import subprocess
import requests
2012-08-19 04:58:14 +02:00
from requests.compat import urlparse
from requests.cookies import RequestsCookieJar, create_cookie
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from argparse import OPTIONAL
2012-08-21 15:45:22 +02:00
from.import __version__
from.config import CONFIG_DIR
from.output import PygmentsProcessor
SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions')
def get_response(name, request_kwargs, read_only=False):
"""Like `client.get_response`, but applies permanent
aspects of the session to the request.
"""
2012-08-19 04:58:14 +02:00
host = Host(request_kwargs['headers'].get('Host', None)
2012-09-07 12:48:59 +02:00
or urlparse(request_kwargs['url']).netloc.split('@')[-1])
2012-08-19 04:58:14 +02:00
session = Session(host, name)
session.load()
# Update session headers with the request headers.
session['headers'].update(request_kwargs.get('headers', {}))
2012-08-19 04:58:14 +02:00
# Use the merged headers for the request
request_kwargs['headers'] = session['headers']
auth = request_kwargs.get('auth', None)
if auth:
session.auth = auth
elif session.auth:
request_kwargs['auth'] = session.auth
rsession = requests.Session(cookies=session.cookies)
try:
response = rsession.request(**request_kwargs)
except Exception:
raise
else:
2012-09-07 12:48:59 +02:00
# Existing sessions with `read_only=True` don't get updated.
if session.is_new or not read_only:
session.cookies = rsession.cookies
session.save()
return response
2012-08-19 04:58:14 +02:00
class Host(object):
"""A host is a per-host directory on the disk containing sessions files."""
2012-08-19 04:58:14 +02:00
def __init__(self, name):
self.name = name
def __iter__(self):
"""Return a iterator yielding `(session_name, session_path)`."""
2012-08-19 04:58:14 +02:00
for fn in sorted(glob.glob1(self.path, '*.json')):
yield os.path.splitext(fn)[0], os.path.join(self.path, fn)
def delete(self):
shutil.rmtree(self.path)
@property
def path(self):
# Name will include ':' if a port is specified, which is invalid
# on windows. DNS does not allow '_' in a domain, or for it to end
# in a number (I think?)
path = os.path.join(SESSIONS_DIR, self.name.replace(':', '_'))
2012-08-19 04:58:14 +02:00
try:
os.makedirs(path, mode=0o700)
except OSError as e:
if e.errno != errno.EEXIST:
2012-08-21 15:45:22 +02:00
raise
2012-08-19 04:58:14 +02:00
return path
@classmethod
def all(cls):
"""Return a generator yielding a host at a time."""
2012-08-19 04:58:14 +02:00
for name in sorted(glob.glob1(SESSIONS_DIR, '*')):
if os.path.isdir(os.path.join(SESSIONS_DIR, name)):
yield Host(name)
class Session(dict):
""""""
2012-08-19 04:58:14 +02:00
def __init__(self, host, name, *args, **kwargs):
super(Session, self).__init__(*args, **kwargs)
2012-08-19 04:58:14 +02:00
self.host = host
self.name = name
2012-08-19 04:58:14 +02:00
self['headers'] = {}
self['cookies'] = {}
2012-08-19 04:58:14 +02:00
def load(self):
try:
with open(self.path, 'rt') as f:
try:
data = json.load(f)
except ValueError as e:
raise ValueError('Invalid session: %s [%s]' %
(e.message, self.path))
self.update(data)
except IOError as e:
if e.errno != errno.ENOENT:
raise
def save(self):
self['__version__'] = __version__
with open(self.path, 'w') as f:
2012-08-19 04:58:14 +02:00
json.dump(self, f, indent=4, sort_keys=True, ensure_ascii=True)
f.write('\n')
2012-08-19 04:58:14 +02:00
def delete(self):
try:
os.unlink(self.path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
@property
def path(self):
return os.path.join(self.host.path, self.name + '.json')
@property
def is_new(self):
return not os.path.exists(self.path)
@property
def cookies(self):
jar = RequestsCookieJar()
for name, cookie_dict in self['cookies'].items():
2012-08-21 15:45:22 +02:00
jar.set_cookie(create_cookie(
name, cookie_dict.pop('value'), **cookie_dict))
jar.clear_expired_cookies()
return jar
@cookies.setter
def cookies(self, jar):
2012-08-19 04:58:14 +02:00
excluded = [
'_rest', 'name', 'port_specified',
'domain_specified', 'domain_initial_dot',
2012-08-19 04:58:14 +02:00
'path_specified', 'comment', 'comment_url'
]
self['cookies'] = {}
for host in jar._cookies.values():
for path in host.values():
for name, cookie in path.items():
cookie_dict = {}
for k, v in cookie.__dict__.items():
2012-08-19 04:58:14 +02:00
if k not in excluded:
cookie_dict[k] = v
self['cookies'][name] = cookie_dict
@property
def auth(self):
auth = self.get('auth', None)
if not auth:
return None
Auth = {'basic': HTTPBasicAuth,
'digest': HTTPDigestAuth}[auth['type']]
return Auth(auth['username'], auth['password'])
@auth.setter
def auth(self, cred):
self['auth'] = {
'type': {HTTPBasicAuth: 'basic',
HTTPDigestAuth: 'digest'}[type(cred)],
'username': cred.username,
'password': cred.password,
}
2012-08-19 04:58:14 +02:00
def list_command(args):
if args.host:
for name, path in Host(args.host):
print(name + ' [' + path + ']')
else:
for host in Host.all():
print(host.name)
for name, path in host:
print(' ' + name + ' [' + path + ']')
2012-08-19 04:58:14 +02:00
def show_command(args):
path = Session(Host(args.host), args.name).path
if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n'
2012-08-21 15:45:22 +02:00
% (args.name, path))
sys.exit(1)
with codecs.open(path, encoding='utf8') as f:
print(path + ':\n')
2012-08-19 04:58:14 +02:00
proc = PygmentsProcessor()
print(proc.process_body(f.read(), 'application/json', 'json'))
print('')
2012-08-19 04:58:14 +02:00
def delete_command(args):
host = Host(args.host)
if not args.name:
2012-08-19 04:58:14 +02:00
host.delete()
else:
2012-08-21 15:45:22 +02:00
Session(host, args.name).delete()
2012-08-19 04:58:14 +02:00
def edit_command(args):
editor = os.environ.get('EDITOR', None)
if not editor:
sys.stderr.write(
'You need to configure the environment variable EDITOR.\n')
sys.exit(1)
command = editor.split()
2012-08-19 04:58:14 +02:00
command.append(Session(Host(args.host), args.name).path)
subprocess.call(command)
2012-08-19 04:58:14 +02:00
def add_commands(subparsers):
2012-08-19 04:58:14 +02:00
# List
list_ = subparsers.add_parser('session-list', help='list sessions')
list_.set_defaults(command=list_command)
list_.add_argument('host', nargs=OPTIONAL)
# Show
show = subparsers.add_parser('session-show', help='show a session')
2012-08-19 04:58:14 +02:00
show.set_defaults(command=show_command)
show.add_argument('host')
show.add_argument('name')
# Edit
2012-08-19 04:58:14 +02:00
edit = subparsers.add_parser(
'session-edit', help='edit a session in $EDITOR')
edit.set_defaults(command=edit_command)
edit.add_argument('host')
edit.add_argument('name')
# Delete
delete = subparsers.add_parser('session-delete', help='delete a session')
2012-08-19 04:58:14 +02:00
delete.set_defaults(command=delete_command)
delete.add_argument('host')
delete.add_argument('name', nargs=OPTIONAL,
2012-08-19 04:58:14 +02:00
help='The name of the session to be deleted.'
' If not specified, all host sessions are deleted.')