mirror of
https://github.com/httpie/cli.git
synced 2026-06-20 11:32:56 +02:00
Added initial support for persistent sessions.
This commit is contained in:
commit
4c0d7d526f
9 files changed
+247
-126
No files matched your search
+1
-1
@@ -3,7 +3,7 @@ HTTPie - cURL for humans.
|
||||
|
||||
"""
|
||||
__author__ = 'Jakub Roztocil'
|
||||
__version__ = '0.2.8dev'
|
||||
__version__ = '0.2.8-alpha'
|
||||
__licence__ = 'BSD'
|
||||
|
||||
|
||||
|
||||
+20
-23
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
|
||||
__author__ = 'jakub'
|
||||
|
||||
|
||||
CONFIG_DIR = os.path.expanduser('~/.httpie')
|
||||
+2
-64
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user