Honor session hooks

Fixes #1655 

TODO:
- [ ] ~~Add tests for `merge_hooks`~~
- [x] Add tests to ensure that Session hooks can be overridden by request hooks.
- [x] Fix logic so that there is no API braking change. Currently `merge_hooks` changes the behaviour of how hooks behave.


@Lukasa I assigned this to you for code review, but don't spend too much time on it yet. I've got work to do.


I'm debating whether `merge_hooks` itself actually needs to be tested directly. We're indirectly testing it through the two tests I added to make sure the behaviour of merging settings/hooks works.

I know we don't test here like I usually do but, I wouldn't be adverse to adding some loose tests around this.


Generally looks good to me. One question: how much do we want to avoid API breaking changes? Feels like the right behaviour here would be to literally _merge_ the hooks.


Yeah I agree but the previously existing behaviour was that hooks set on the 
request over-rode the ones on the Session. That said, I wonder what happens 
with auth hooks and if perhaps I'm mis-remembering the old behaviour.


No, I think you're right about the old behaviour. Let's fix this up now, and we can potentially give ourselves a work item to do something better in a future major release.


@Lukasa any further review on the code? 

In reality, if we change the future behaviour it's a really simple way of actually merging the two sets of hooks. I had that in my first stab at this [here](https://github.com/sigmavirus24/requests/commit/5bf396d5d12bf9debc667a509c84f640560da517#diff-28e67177469c0d36b068d68d9f6043bfR84).


No further review, I'm happy with this. =) Suitably forward looking as well.


:+1: Would like to see this as well!


BTW, as I was playing around with this, it would be nice if it handled naked functions by slapping them into a set:

``` python
def merge_hooks(request_hooks, session_hooks):
    """
    Properly merges both requests and session hooks.

    This is necessary because when request_hooks == {'response': []}, the
    merge breaks Session hooks entirely.
    """
    ensure_set = lambda f: set([f]) if callable(f) else set(f)

    if session_hooks is None or session_hooks.get('response') == []:
        return request_hooks

    if request_hooks is None or request_hooks.get('response') == []:
        return session_hooks

    ret = {}
    for (k, v) in request_hooks.items():
        if v is not None:
            ret[k] = ensure_set(v).union(ensure_set(session_hooks.get(k, [])))

    return ret
```

Or similar, since this should work:

``` python
import requests

hooks_called = []
session = requests.Session()
session.hooks = {'response': lambda r, **kw: (hooks_called.append('HELLO') or r)}

request = requests.Request(
    'GET',
    'http://httpbin.org/get',
    hooks={'response': lambda r, **kw: (hooks_called.append('WORLD') or r)}
)
prepared = request.prepare()
response = session.send(prepared)

assert hooks_called == ['HELLO', 'WORLD']
```

Feel free to use the code above in whatever you like! I can wrap it up in a PR to your branch if that makes it easier.


(Although, naive sets don't maintain their ordering...)


Also `callable` is no longer available on Python 3. The correct way to test that is to do `isinstance(f, collections.Callable)` but that smells and I would rather never try to pre-empt a user from shooting themselves in the foot.


> Also callable is no longer available on Python 3.

Ah! I didn't know, thanks!

> I would rather never try to pre-empt a user from shooting themselves in the foot

Could you explain?


Ah, I see. Basically, don't modify the structure of hook/hooks. That makes perfect sense in hindsight. (I also missed your follow up https://github.com/sigmavirus24/requests/commit/a4605fe32426bcc404ab1eb4ea4b0dde9238a3e8)

There is another place where session hooks & prepared hooks don't merge (Session.send() which I mentioned in issue 1655). Does it make sense to wrap that up in here or is that a new issue?


I also mistook Session.send() as doing the mixing, instead the request should be prepared with `session.prepare_request(request)` first, then sent. All is well! I'll show myself out... :smiley: 


And I misremembered `callable` being removed in Python 3. :smile: 


@Lukasa @kennethreitz can we get this merged soon? I just realized I'll probably need this for github3.py so that I don't have to do [this](https://github.com/sigmavirus24/github3.py/commit/9a1b4d892a31bd12ee477f39caada76670cfc062#diff-8a0b7651f0a27fb4490e3f2d87206eedR67) ;)


I was wondering that too!