1
0
mirror of https://github.com/httpie/cli.git synced 2025-06-17 00:17:45 +02:00

JSON session data, `httpie' management command.

This commit is contained in:
Jakub Roztocil
2012-08-18 23:03:31 +02:00
parent ff9f23da5b
commit 8a9cedb16e
8 changed files with 253 additions and 79 deletions

View File

@ -506,31 +506,30 @@ path. The path can also be configured via the environment variable
Sessions Sessions
======== ========
*This is an experimental feature.*
HTTPie supports named sessions, where several options and cookies sent HTTPie supports named sessions, where several options and cookies sent
by the server persist between requests: by the server persist between requests:
.. code-block:: bash .. code-block:: bash
http --session=user1 --auth=user1:password example.org http --session=user1 --auth=user1:password example.org X-Foo:Bar
Now you can always refer to the session by passing ``--session=user1``, Now you can always refer to the session by passing ``--session=user1``,
and the credentials and cookies will be reused: and the credentials, custom headers and cookies will be reused:
.. code-block:: bash .. code-block:: bash
http --session=user1 GET example.org http --session=user1 example.org
Since sessions are named, you can switch between multiple sessions:
.. code-block:: bash
http --session=user2 --auth=user2:password example.org
Note that session cookies respect the cookie domain and path. Note that session cookies respect the cookie domain and path.
Session data are stored in ``~/.httpie/sessions/<name>.pickle``.
Session data are stored in ``~/.httpie/sessions/<name>.json``.
You can view and manipulate existing sessions via the ``httpie`` management
command, see ``httpie --help``.
============== ==============

View File

@ -224,11 +224,10 @@ misc.add_argument(
'--session', metavar='SESSION_NAME', '--session', metavar='SESSION_NAME',
help=_(''' help=_('''
Create or reuse a session. Create or reuse a session.
Withing a session, values of --auth, --timeout, Withing a session, custom headers, auth credential, as well as any
--verify, --proxies, headers, as well as any cookies sent by the server persist between requests.
cookies sent by the server are persistent between requests.
You can use the `httpie' management command to manipulate You can use the `httpie' management command to manipulate
and inspect existing sessions. See `httpie session'. and inspect existing sessions. See `httpie --help'.
''') ''')
) )

View File

@ -4,12 +4,15 @@ from pprint import pformat
import requests import requests
import requests.auth import requests.auth
from requests.defaults import defaults
from .import sessions from . import sessions
from . import __version__
FORM = 'application/x-www-form-urlencoded; charset=utf-8' FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8' JSON = 'application/json; charset=utf-8'
DEFAULT_UA = 'HTTPie/%s' % __version__
def get_response(args): def get_response(args):
@ -29,25 +32,24 @@ def get_response(args):
def get_requests_kwargs(args): def get_requests_kwargs(args):
"""Send the request and return a `request.Response`.""" """Send the request and return a `request.Response`."""
base_headers = defaults['base_headers'].copy()
base_headers['User-Agent'] = DEFAULT_UA
auto_json = args.data and not args.form auto_json = args.data and not args.form
if args.json or auto_json: if args.json or auto_json:
if 'Content-Type' not in args.headers and args.data: base_headers['Accept'] = 'application/json'
args.headers['Content-Type'] = JSON if args.data:
base_headers['Content-Type'] = JSON
if 'Accept' not in args.headers:
# Default Accept to JSON as well.
args.headers['Accept'] = 'application/json'
if isinstance(args.data, dict): if isinstance(args.data, dict):
# If not empty, serialize the data `dict` parsed from arguments. # If not empty, serialize the data `dict` parsed from arguments.
# Otherwise set it to `None` avoid sending "{}". # Otherwise set it to `None` avoid sending "{}".
args.data = json.dumps(args.data) if args.data else None args.data = json.dumps(args.data) if args.data else None
elif args.form: elif args.form and not args.files:
if not args.files and 'Content-Type' not in args.headers:
# If sending files, `requests` will set # If sending files, `requests` will set
# the `Content-Type` for us. # the `Content-Type` for us.
args.headers['Content-Type'] = FORM base_headers['Content-Type'] = FORM
credentials = None credentials = None
if args.auth: if args.auth:
@ -71,7 +73,10 @@ def get_requests_kwargs(args):
'proxies': dict((p.key, p.value) for p in args.proxy), 'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files, 'files': args.files,
'allow_redirects': args.allow_redirects, 'allow_redirects': args.allow_redirects,
'params': args.params 'params': args.params,
'config': {
'base_headers': base_headers
}
} }
return kwargs return kwargs

View File

@ -18,8 +18,6 @@ except ImportError:
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from requests.compat import str, urlparse from requests.compat import str, urlparse
from . import __version__
HTTP_POST = 'POST' HTTP_POST = 'POST'
HTTP_GET = 'GET' HTTP_GET = 'GET'
@ -79,7 +77,6 @@ PRETTY_STDOUT_TTY_ONLY = object()
# Defaults # Defaults
OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY
OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY
DEFAULT_UA = 'HTTPie/%s' % __version__
class Parser(argparse.ArgumentParser): class Parser(argparse.ArgumentParser):
@ -193,7 +190,6 @@ class Parser(argparse.ArgumentParser):
""" """
args.headers = CaseInsensitiveDict() args.headers = CaseInsensitiveDict()
args.headers['User-Agent'] = DEFAULT_UA
args.data = ParamDict() if args.form else OrderedDict() args.data = ParamDict() if args.form else OrderedDict()
args.files = OrderedDict() args.files = OrderedDict()
args.params = ParamDict() args.params = ParamDict()

30
httpie/manage.py Normal file
View File

@ -0,0 +1,30 @@
"""
Provides the `httpie' management command.
Note that the main `http' command points to `httpie.__main__.main()`.
"""
import argparse
from . import sessions
from . import __version__
parser = argparse.ArgumentParser(
description='The HTTPie management command.',
version=__version__
)
subparsers = parser.add_subparsers()
# Only sessions as of now.
sessions.add_actions(subparsers)
def main():
args = parser.parse_args()
args.action(args)
if __name__ == '__main__':
main()

View File

@ -135,7 +135,7 @@ def make_stream(env, args):
elif args.prettify: elif args.prettify:
Stream = partial( Stream = partial(
PrettyStream if args.stream else BufferedPrettyStream, PrettyStream if args.stream else BufferedPrettyStream,
processor=OutputProcessor(env, groups=args.prettify, processor=OutputProcessor(env=env, groups=args.prettify,
pygments_style=args.style), pygments_style=args.style),
env=env) env=env)
else: else:
@ -343,7 +343,7 @@ class BaseProcessor(object):
enabled = True enabled = True
def __init__(self, env, **kwargs): def __init__(self, env=Environment(), **kwargs):
""" """
:param env: an class:`Environment` instance :param env: an class:`Environment` instance
:param kwargs: additional keyword argument that some :param kwargs: additional keyword argument that some
@ -406,7 +406,8 @@ class PygmentsProcessor(BaseProcessor):
return return
try: try:
style = get_style_by_name(self.kwargs['pygments_style']) style = get_style_by_name(
self.kwargs.get('pygments_style', DEFAULT_STYLE))
except ClassNotFound: except ClassNotFound:
style = Solarized256Style style = Solarized256Style
@ -460,7 +461,7 @@ class OutputProcessor(object):
] ]
} }
def __init__(self, env, groups, **kwargs): def __init__(self, groups, env=Environment(), **kwargs):
""" """
:param env: a :class:`models.Environment` instance :param env: a :class:`models.Environment` instance
:param groups: the groups of processors to be applied :param groups: the groups of processors to be applied

View File

@ -1,68 +1,211 @@
import os """Persistent, JSON-serialized sessions.
import pickle
import errno
from requests import Session
"""
import os
import sys
import json
import glob
import errno
import codecs
import subprocess
from requests import Session as RSession
from requests.cookies import RequestsCookieJar, create_cookie
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from . import __version__
from .config import CONFIG_DIR from .config import CONFIG_DIR
from .output import PygmentsProcessor
SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions') SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions')
def get_response(name, request_kwargs): def get_response(name, request_kwargs):
session = load(name)
session_kwargs, request_kwargs = split_kwargs(request_kwargs) session = Session.load(name)
headers = session_kwargs.pop('headers', None)
if headers: # Update session headers with the request headers.
session.headers.update(headers) session['headers'].update(request_kwargs.get('headers', {}))
session.__dict__.update(session_kwargs)
auth = request_kwargs.get('auth', None)
if auth:
session.auth = auth
elif session.auth:
request_kwargs['auth'] = session.auth
# Use the merged headers for the request
request_kwargs['headers'] = session['headers']
rsession = RSession(cookies=session.cookies)
try: try:
response = session.request(**request_kwargs) response = rsession.request(**request_kwargs)
except Exception: except Exception:
raise raise
else: else:
save(session, name) session.cookies = rsession.cookies
session.save()
return response return response
def split_kwargs(requests_kwargs): class Session(dict):
session = {}
request = {} def __init__(self, name, *args, **kwargs):
session_attrs = [ super(Session, self).__init__(*args, **kwargs)
'auth', 'timeout', self.name = name
'verify', 'proxies', self.setdefault('cookies', {})
'params' self.setdefault('headers', {})
@property
def path(self):
return type(self).get_path(self.name)
@property
def cookies(self):
jar = RequestsCookieJar()
for name, cookie_dict in self['cookies'].items():
cookie = create_cookie(
name, cookie_dict.pop('value'), **cookie_dict)
jar.set_cookie(cookie)
jar.clear_expired_cookies()
return jar
@cookies.setter
def cookies(self, jar):
exclude = [
'_rest', 'name', 'port_specified',
'domain_specified', 'domain_initial_dot',
'path_specified'
] ]
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():
if k not in exclude:
cookie_dict[k] = v
self['cookies'][name] = cookie_dict
for k, v in requests_kwargs.items(): @property
if v is not None: def auth(self):
if k in session_attrs: auth = self.get('auth', None)
session[k] = v if not auth:
else: return None
request[k] = v Auth = {'basic': HTTPBasicAuth,
return session, request 'digest': HTTPDigestAuth}[auth['type']]
return Auth(auth['username'], auth['password'])
def get_path(name): @auth.setter
def auth(self, cred):
self['auth'] = {
'type': {HTTPBasicAuth: 'basic',
HTTPDigestAuth: 'digest'}[type(cred)],
'username': cred.username,
'password': cred.password,
}
def save(self):
self['__version__'] = __version__
with open(self.path, 'wb') as f:
json.dump(self, f, indent=4, sort_keys=True, ensure_ascii=True)
f.write(b'\n')
@classmethod
def load(cls, name):
try:
with open(cls.get_path(name), 'rt') as f:
try:
data = json.load(f)
except ValueError as e:
raise ValueError('Invalid session: %s [%s]' %
(e.message, f.name))
return cls(name, data)
except IOError as e:
if e.errno != errno.ENOENT:
raise
return cls(name)
@classmethod
def get_path(cls, name):
try: try:
os.makedirs(SESSIONS_DIR, mode=0o700) os.makedirs(SESSIONS_DIR, mode=0o700)
except OSError as e: except OSError as e:
if e.errno != errno.EEXIST: if e.errno != errno.EEXIST:
raise raise
return os.path.join(SESSIONS_DIR, name + '.pickle') return os.path.join(SESSIONS_DIR, name + '.json')
def load(name): def show_action(args):
try: if not args.name:
with open(get_path(name), 'rb') as f: for fn in sorted(glob.glob1(SESSIONS_DIR, '*.json')):
return pickle.load(f) print(os.path.splitext(fn)[0])
except IOError as e: return
if e.errno != errno.ENOENT:
raise path = Session.get_path(args.name)
return Session() if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n'
% (args.name, path))
sys.exit(1)
with codecs.open(path, encoding='utf8') as f:
print(path + ':\n')
print(PygmentsProcessor().process_body(
f.read(), 'application/json', 'json'))
print('')
def save(session, name): def delete_action(args):
with open(get_path(name), 'wb') as f: if not args.name:
pickle.dump(session, f) for path in glob.glob(os.path.join(SESSIONS_DIR, '*.json')):
os.unlink(path)
return
path = Session.get_path(args.name)
if not os.path.exists(path):
sys.stderr.write('Session "%s" does not exist [%s].\n'
% (args.name, path))
sys.exit(1)
else:
os.unlink(path)
def edit_action(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()
command.append(Session.get_path(args.name))
subprocess.call(command)
def add_actions(subparsers):
# Show
show = subparsers.add_parser('session-show', help='list or show sessions')
show.set_defaults(action=show_action)
show.add_argument('name', nargs='?',
help='When omitted, HTTPie prints a list of existing sessions.'
' When specified, the session data is printed.')
# Edit
edit = subparsers.add_parser('session-edit', help='edit a session in $EDITOR')
edit.set_defaults(action=edit_action)
edit.add_argument('name')
# Delete
delete = subparsers.add_parser('session-delete', help='delete a session')
delete.set_defaults(action=delete_action)
delete_group = delete.add_mutually_exclusive_group(required=True)
delete_group.add_argument(
'--all', action='store_true',
help='Delete all sessions from %s' % SESSIONS_DIR)
delete_group.add_argument(
'name', nargs='?',
help='The name of the session to be deleted. ' \
'To see a list existing sessions, run `httpie sessions show\'.')

View File

@ -47,6 +47,7 @@ setup(
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'http = httpie.__main__:main', 'http = httpie.__main__:main',
'httpie = httpie.manage:main',
], ],
}, },
install_requires=requirements, install_requires=requirements,