Welcome to Clients’ documentation.

HTTP for lazy, impatient, hubristic humans.

Quickstart

As great as requests is, typical usage is falling into some anti-patterns.

  • Being url-based, realistically all code needs to deal with url joining. Which tends to be redundant and suffer from leading or trailing slash issues.
  • The module level methods don’t take advantage of connection pooling, and require duplicate settings. Given the “100% automatic” documentation of connection reuse, it’s unclear how widely known this is.
  • Using Sessions requires assigning every setting individually, and still requires url joining.

Clients aim to be encourage best practices by making Sessions even easier to use than the module methods.

client = clients.Client('http://httpbin.org/',
                        auth=('user', 'pass'), headers={'x-test': 'true'})
r = client.get('headers', headers={'x-test2': 'true'})
assert {'x-test', 'x-test2'} <= set(r.request.headers)

r = client.get('cookies', cookies={'from-my': 'browser'})
assert r.json() == {'cookies': {'from-my': 'browser'}}
r = client.get('cookies')
assert r.json() == {'cookies': {}}

client.get('cookies/set', params={'sessioncookie': '123456789'})
r = client.get('cookies')
assert r.json() == {'cookies': {'sessioncookie': '123456789'}}

Which reveals another anti-pattern regarding Responses. Although the response object is sometimes required, naturally the most common use case is to access the content. But the onus is on the caller to check the status_code and content-type.

Resources aim to making writing custom api clients or sdks easier. Their primary feature is to allow direct content access without silencing errors. Response content type is inferred from headers: json, content, or text.

resource = clients.Resource('http://httpbin.org/')
assert resource.get('get')['url'] == 'http://httpbin.org/get'
with pytest.raises(IOError):
    resource.get('status/404')
assert '<html>' in resource.get('html')
assert isinstance(resource.get('bytes/10'), bytes)

Advanced Usage

Clients allow any base url, not just hosts, and consequently support path concatenation. Following the semantics of urljoin however, absolute paths and urls are treated as such. Hence there’s no need to parse a url retrieved from an api.

client = clients.Client('http://httpbin.org/')
cookies = client / 'cookies'
assert isinstance(cookies, clients.Client)
assert cookies.get().url == 'http://httpbin.org/cookies'

assert cookies.get('/').url == 'http://httpbin.org/'
assert cookies.get('http://httpbin.org/').url == 'http://httpbin.org/'

Some api endpoints require trailing slashes; some forbid them. Set it and forget it.

client = clients.Client('http://httpbin.org/', trailing='/')
assert client.get('ip').status_code == 404

Note trailing doesn’t technically have to be a slash. This can be useful for static paths below a parameter: .../<user>/profile.

Asyncio

Using aiohttp instead of requests, AsyncClients and AsyncResources implement the same interface, except the request methods return asyncio coroutines.

Avant-garde Usage

Note

These experimental interfaces may obviate the need for custom clients altogether.

Resources support operator overloaded syntax wherever sensible.

  • __getattr__: alternate path concatenation
  • __getitem__: GET content
  • __setitem__: PUT json
  • __delitem__: DELETE
  • __contains__: HEAD ok
  • __iter__: GET streamed lines or content
  • __call__: GET with params
resource = clients.Resource('http://httpbin.org/')
assert set(resource['get']) == {'origin', 'headers', 'args', 'url'}
resource['put'] = {}
del resource['delete']

assert '200' in resource.status
assert '404' not in resource.status
assert [line['id'] for line in resource / 'stream/3'] == [0, 1, 2]
assert next(iter(resource / 'html')) == '<!DOCTYPE html>'
assert resource('cookies/set', name='value') == {'cookies': {'name': 'value'}}

Higher-level methods for common requests.

  • iter: __iter__ with args
  • update: PATCH with json params
  • create: POST and return location
  • download: GET streamed content to file
resource = clients.Resource('http://httpbin.org/')
assert list(map(len, resource.iter('stream-bytes/256'))) == [128] * 2
assert resource.update('patch', name='value')['json'] == {'name': 'value'}
assert resource.create('post', {'name': 'value'}) is None
file = resource.download(io.BytesIO(), 'image/png')
assert file.tell()

A singleton decorator can be used on subclasses, conveniently creating a single custom instance.

@clients.singleton(url)
class custom_api(clients.Resource):
    pass  # custom methods

assert isinstance(custom_api, clients.Resource)
assert custom_api.url == url

Indices and tables