Added initial support for persistent sessions.

This commit is contained in:
Jakub Roztocil committed 2012-08-17 23:30:47 +02:00
commit 4c0d7d526f
9 files changed
+247 -126

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ HTTPie - cURL for humans.
"""
__author__ = 'Jakub Roztocil'
__version__ = '0.2.8dev'
__version__ = '0.2.8-alpha'
__licence__ = 'BSD'
+20 -23
View File
@@ -14,8 +14,7 @@ from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS,
PRETTY_STDOUT_TTY_ONLY, PRETTY_ALL,
PRETTY_FORMAT, PRETTY_COLORS)
PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY)
def _(text):
@@ -69,29 +68,17 @@ parser.add_argument(
)
)
prettify = parser.add_mutually_exclusive_group(required=False)
prettify.add_argument(
'--pretty', dest='prettify', action='store_const', const=PRETTY_ALL,
default=PRETTY_STDOUT_TTY_ONLY,
parser.add_argument(
'--pretty', dest='prettify', default=PRETTY_STDOUT_TTY_ONLY,
choices=sorted(PRETTY_MAP.keys()),
help=_('''
Apply both colors and formatting. Default for terminal output.
Controls output processing. The value can be "none" to not prettify
the output (default for redirected output), "all" to apply both colors
and formatting
(default for terminal output), "colors", or "format".
''')
)
prettify.add_argument(
'--colors', dest='prettify', action='store_const', const=PRETTY_COLORS,
help=_('''Apply colors to the output.''')
)
prettify.add_argument(
'--format', dest='prettify', action='store_const', const=PRETTY_FORMAT,
help=_('''Apply formatting to the output.''')
)
prettify.add_argument(
'--ugly', '-u', dest='prettify', action='store_false',
help=_('''
Disables output processing.
Default for redirected output.
''')
)
output_options = parser.add_mutually_exclusive_group(required=False)
output_options.add_argument('--print', '-p', dest='output_options',
@@ -182,9 +169,19 @@ parser.add_argument(
''')
)
parser.add_argument(
'--session', metavar='NAME',
help=_('''
Create or reuse a session.
Withing a session, values of --auth, --timeout,
--verify, --proxies are persistent, as well as any
cookies sent by the server.
''')
)
# ``requests.request`` keyword arguments.
parser.add_argument(
'--auth', '-a', metavar='USER:PASS',
'--auth', '-a', metavar='USER[:PASS]',
type=AuthCredentialsArgType(SEP_CREDENTIALS),
help=_('''
username:password.
+77
View File
@@ -0,0 +1,77 @@
import json
import sys
from pprint import pformat
import requests
import requests.auth
from .import sessions
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
def get_response(args):
requests_kwargs = get_requests_kwargs(args)
if args.debug:
sys.stderr.write(
'\n>>> requests.request(%s)\n\n' % pformat(requests_kwargs))
if args.session:
return sessions.get_response(args.session, requests_kwargs)
else:
return requests.request(**requests_kwargs)
def get_requests_kwargs(args):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form
if args.json or auto_json:
if 'Content-Type' not in args.headers and args.data:
args.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 not empty, serialize the data `dict` parsed from arguments.
# Otherwise set it to `None` avoid sending "{}".
args.data = json.dumps(args.data) if args.data else None
elif args.form:
if not args.files and 'Content-Type' not in args.headers:
# If sending files, `requests` will set
# the `Content-Type` for us.
args.headers['Content-Type'] = FORM
credentials = None
if args.auth:
credentials = {
'basic': requests.auth.HTTPBasicAuth,
'digest': requests.auth.HTTPDigestAuth,
}[args.auth_type](args.auth.key, args.auth.value)
kwargs = {
'prefetch': False,
'method': args.method.lower(),
'url': args.url,
'headers': args.headers,
'data': args.data,
'verify': {
'yes': True,
'no': False
}.get(args.verify,args.verify),
'timeout': args.timeout,
'auth': credentials,
'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files,
'allow_redirects': args.allow_redirects,
'params': args.params
}
return kwargs
+6
View File
@@ -0,0 +1,6 @@
import os
__author__ = 'jakub'
CONFIG_DIR = os.path.expanduser('~/.httpie')
+2 -64
View File
@@ -11,77 +11,21 @@ Invocation flow:
"""
import sys
import json
import errno
from pprint import pformat
import requests
import requests.auth
from requests.compat import str, is_py3
from httpie import __version__ as httpie_version
from requests import __version__ as requests_version
from pygments import __version__ as pygments_version
from .cli import parser
from .client import get_response
from .models import Environment
from .output import output_stream, write, write_with_colors_win_p3k
from . import EXIT
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
def get_requests_kwargs(args):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form
if args.json or auto_json:
if 'Content-Type' not in args.headers and args.data:
args.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 not empty, serialize the data `dict` parsed from arguments.
# Otherwise set it to `None` avoid sending "{}".
args.data = json.dumps(args.data) if args.data else None
elif args.form:
if not args.files and 'Content-Type' not in args.headers:
# If sending files, `requests` will set
# the `Content-Type` for us.
args.headers['Content-Type'] = FORM
credentials = None
if args.auth:
credentials = {
'basic': requests.auth.HTTPBasicAuth,
'digest': requests.auth.HTTPDigestAuth,
}[args.auth_type](args.auth.key, args.auth.value)
kwargs = {
'prefetch': False,
'method': args.method.lower(),
'url': args.url,
'headers': args.headers,
'data': args.data,
'verify': {
'yes': True,
'no': False
}.get(args.verify,args.verify),
'timeout': args.timeout,
'auth': credentials,
'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files,
'allow_redirects': args.allow_redirects,
'params': args.params
}
return kwargs
def get_exist_status(code, allow_redirects=False):
"""Translate HTTP status code to exit status."""
@@ -121,13 +65,7 @@ def main(args=sys.argv[1:], env=Environment()):
try:
args = parser.parse_args(args=args, env=env)
requests_kwargs = get_requests_kwargs(args)
if args.debug:
sys.stderr.write(
'\n>>> requests.request(%s)\n\n' % pformat(requests_kwargs))
response = requests.request(**requests_kwargs)
response = get_response(args)
if args.check_status:
status = get_exist_status(response.status_code,
+15 -7
View File
@@ -67,9 +67,12 @@ OUTPUT_OPTIONS = frozenset([
])
# Pretty
PRETTY_ALL = ['format', 'colors']
PRETTY_FORMAT = ['format']
PRETTY_COLORS = ['colors']
PRETTY_MAP = {
'all': ['format', 'colors'],
'colors': ['colors'],
'format': ['format'],
'none': []
}
PRETTY_STDOUT_TTY_ONLY = object()
@@ -114,6 +117,7 @@ class Parser(argparse.ArgumentParser):
env.stdout_isatty = False
self._process_output_options(args, env)
self._process_pretty_options(args, env)
self._guess_method(args, env)
self._parse_items(args)
@@ -128,10 +132,6 @@ class Parser(argparse.ArgumentParser):
# Stdin already read (if not a tty) so it's save to prompt.
args.auth.prompt_password(urlparse(args.url).netloc)
if args.prettify == PRETTY_STDOUT_TTY_ONLY:
args.prettify = PRETTY_ALL if env.stdout_isatty else []
elif args.prettify and env.is_windows:
self.error('Only terminal output can be prettified on Windows.')
return args
@@ -246,6 +246,14 @@ class Parser(argparse.ArgumentParser):
if unknown:
self.error('Unknown output options: %s' % ','.join(unknown))
def _process_pretty_options(self, args, env):
if args.prettify == PRETTY_STDOUT_TTY_ONLY:
args.prettify = PRETTY_MAP['all' if env.stdout_isatty else 'none']
elif args.prettify and env.is_windows:
self.error('Only terminal output can be prettified on Windows.')
else:
args.prettify = PRETTY_MAP[args.prettify]
class ParseError(Exception):
pass
+68
View File
@@ -0,0 +1,68 @@
import os
import pickle
import errno
from requests import Session
from .config import CONFIG_DIR
SESSIONS_DIR = os.path.join(CONFIG_DIR, 'sessions')
def get_response(name, request_kwargs):
session = load(name)
session_kwargs, request_kwargs = split_kwargs(request_kwargs)
headers = session_kwargs.pop('headers', None)
if headers:
session.headers.update(headers)
session.__dict__.update(session_kwargs)
try:
response = session.request(**request_kwargs)
except Exception:
raise
else:
save(session, name)
return response
def split_kwargs(requests_kwargs):
session = {}
request = {}
session_attrs = [
'auth', 'timeout',
'verify', 'proxies',
'params'
]
for k, v in requests_kwargs.items():
if v is not None:
if k in session_attrs:
session[k] = v
else:
request[k] = v
return session, request
def get_path(name):
try:
os.makedirs(SESSIONS_DIR, mode=0o700)
except OSError as e:
if e.errno != errno.EEXIST:
raise
return os.path.join(SESSIONS_DIR, name + '.pickle')
def load(name):
try:
with open(get_path(name), 'rb') as f:
return pickle.load(f)
except IOError as e:
if e.errno != errno.ENOENT:
raise
return Session()
def save(session, name):
with open(get_path(name), 'wb') as f:
pickle.dump(session, f)