Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for brotli content encoding via brotlipy package #1532

Merged
merged 4 commits into from
Jan 29, 2019

Conversation

immerrr
Copy link
Contributor

@immerrr immerrr commented Jan 28, 2019

This is A PR to add optional support for brotlipy package to implement support for brotli content encoding. There was another PR that attempted to do this, #713, but it was closed during the PR bankruptcy and never reopened back. This PR is slightly different, because unlike #713 it doesn't go the way of creating an extensible decoders subpackage only adding the bare minimum.

@sethmlarson
Copy link
Member

I wish our architecture was more pluggable. :)

Copy link
Member

@sethmlarson sethmlarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting you some early review comments.

.gitignore Outdated
@@ -6,3 +6,4 @@
/dist
/build
/docs/_build
/venv
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revert this change as it's not critical for this patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's temporary and won't go to the final commit.

@@ -118,6 +138,9 @@ def _get_decoder(mode):
if mode == 'gzip':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do a if and elif chain here for better readability.

Copy link
Contributor Author

@immerrr immerrr Jan 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are competing schools of thought regarding this: one is about having "if-elif-elif-else" wherever you can, the other is about "if your "if" ends with a return, there is no need to add "else" and an extra level of indentation".

I felt like the latter was applied in this section of the code, but I'm happy to change it if you like.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave the code how it is.

def decompress(self, data):
try:
return self._obj.decompress(data)
except brotli.Error as err:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to re-raise here if we're adding brotli.Error below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@@ -154,7 +177,7 @@ class is also compatible with the Python standard library's :mod:`io`
value of Content-Length header, if present. Otherwise, raise error.
"""

CONTENT_DECODERS = ['gzip', 'deflate']
CONTENT_DECODERS = ['gzip', 'deflate', 'br']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to conditionally add 'br' to CONTENT_DECODERS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you

import brotli
except ImportError:
brotli = None
if brotli is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can roll this += logic within the try/except.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

test/__init__.py Outdated
@@ -74,6 +78,16 @@ def wrapper(*args, **kwargs):
return wrapper


def only_with_brotlipy():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the semantics of notX and requiresX consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

tox.ini Outdated
@@ -1,9 +1,11 @@
[tox]
envlist = flake8-py3, py27, py34, py35, py36, py37, py38, pypy
envlist = flake8-py3, py27, py34, py35, py36, py37, py38, pypy, py{27,38}-nobrotli
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wish there was a better way to handle this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not very good with tox, and open to constructive criticism here.

@codecov-io
Copy link

codecov-io commented Jan 28, 2019

Codecov Report

Merging #1532 into master will decrease coverage by 0.27%.
The diff coverage is 82.6%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1532      +/-   ##
==========================================
- Coverage   99.94%   99.67%   -0.28%     
==========================================
  Files          22       22              
  Lines        1806     1826      +20     
==========================================
+ Hits         1805     1820      +15     
- Misses          1        6       +5
Impacted Files Coverage Δ
src/urllib3/util/request.py 97.43% <100%> (-2.57%) ⬇️
src/urllib3/response.py 98.87% <80.95%> (-1.13%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a90d3f3...60190e6. Read the comment docs.

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

I wish our architecture was more pluggable. :)

We can try and go the other route, that is to first introduce a pluggable decoder package and then add brotli on top of it.

@sethmlarson
Copy link
Member

@immerrr The other route isn't great either because the default should be "always-on" if brotlipy is available. I guess I meant I wish our architecture was easier to work (for us) with for dependency-requiring optional functionality.

Few other things I didn't comment about:

  • We need a changelog entry for this.
  • We need to update our travis.yml to run tests without brotlipy to test the negative cases.

@immerrr immerrr changed the title WIP: Add optional brotli decoder Add support for brotli content encoding via brotlipy package Jan 28, 2019
@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

@sethmlarson pluggable architecture could be "always-on if available", no problem. I'll put together a POC shortly.

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

We need a changelog entry for this.

Done

We need to update our travis.yml to run tests without brotlipy to test the negative cases.

And done. Not sure what do make of the error in "docs" target in Travis.

@sethmlarson
Copy link
Member

I'm unsure what to make of it either. Will have to investigate.

@pquentin
Copy link
Member

The DOCS failure on Travis and the Python 2.7 failure on Appveyor look like temporary network failures

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

Looks like the docs failure is persistent. Actually, there are two.

One is this one:

Traceback (most recent call last):
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/sphinx/ext/intersphinx.py", line 250, in load_mappings
    invdata = fetch_inventory(app, uri, inv)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/sphinx/ext/intersphinx.py", line 181, in fetch_inventory
    f = _read_from_url(inv, config=app.config)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/sphinx/ext/intersphinx.py", line 135, in _read_from_url
    r = requests.get(url, stream=True, config=config, timeout=config.intersphinx_timeout)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/sphinx/util/requests.py", line 144, in get
    return requests.get(url, **kwargs)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/api.py", line 72, in get
    return request('get', url, params=params, **kwargs)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/sessions.py", line 522, in request
    resp = self.send(prep, **send_kwargs)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/sessions.py", line 642, in send
    r = adapter.send(request, **kwargs)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/adapters.py", line 439, in send
    timeout=timeout
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/connectionpool.py", line 600, in urlopen
    chunked=chunked)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/connectionpool.py", line 345, in _make_request
    self._validate_conn(conn)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/connectionpool.py", line 844, in _validate_conn
    conn.connect()
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/connection.py", line 337, in connect
    cert = self.sock.getpeercert()
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 345, in getpeercert
    'subjectAltName': get_subj_alt_name(x509)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 219, in get_subj_alt_name
    for name in ext.get_values_for_type(x509.DNSName)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 219, in <listcomp>
    for name in ext.get_values_for_type(x509.DNSName)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 175, in _dnsname_to_stdlib
    name = idna_encode(name)
  File "/home/immerrr/src/urllib3/.tox/docs/lib/python3.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 167, in idna_encode
    import idna
ModuleNotFoundError: No module named 'idna'

The other is the fact that intersphinx fails to print that message to show what exactly has failed and prints a quite confusing

Warning, treated as error:
failed to reach any of the inventories with the following issues:
<end of output>

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

Once you have the docs tox env the error is reproduceable with

$ .tox/docs/bin/python -c 'import requests; requests.get("https://docs.python.org")'

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

Looks like a problem with dependencies that were vendored with requests until 2.16, if I update requests to 2.16 it looks OK

@immerrr
Copy link
Contributor Author

immerrr commented Jan 28, 2019

Found the problem. I had to explicitly name "extras" on the "docs" target in tox.ini after overriding them conditionally in the generic testenvs section.

@pquentin
Copy link
Member

Nice catch!

@immerrr immerrr force-pushed the add-brotli-decoder branch 3 times, most recently from aca64f4 to 22a9ad3 Compare January 29, 2019 15:35
@immerrr
Copy link
Contributor Author

immerrr commented Jan 29, 2019

Fixed all outstanding issues, I believe this PR is ready for another round of reviews.

Copy link
Member

@theacodes theacodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking real good, just one comment from me.

setup.py Outdated
@@ -64,6 +64,9 @@
],
test_suite='test',
extras_require={
'brotli': [
'brotlipy',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we at least set a minimum bound here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend >=0.6.0 so we can use brotli.Error and we fix a decompress() bug. See: https://github.com/python-hyper/brotlipy/blob/master/HISTORY.rst#060-2016-09-08

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @immerrr I edited my comment as you pushed. Would you mind increasing to 0.6.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

@immerrr immerrr force-pushed the add-brotli-decoder branch 2 times, most recently from ad1e66f to 31b4384 Compare January 29, 2019 19:00
Copy link
Member

@sethmlarson sethmlarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending CI passing I think this looks ready to merge.

@sethmlarson
Copy link
Member

Transient failures on CI

@sethmlarson sethmlarson reopened this Jan 29, 2019
@sethmlarson sethmlarson merged commit 9eecb6b into urllib3:master Jan 29, 2019
@sethmlarson
Copy link
Member

Thanks @immerrr! 🎉

@immerrr immerrr deleted the add-brotli-decoder branch January 30, 2019 00:01
@immerrr
Copy link
Contributor Author

immerrr commented Jan 30, 2019

Thank you for helping me to see it through.

@smasty
Copy link

smasty commented Apr 10, 2019

Any idea when this might be released?

@sethmlarson
Copy link
Member

@smasty There is still one outstanding release-blocker #1531 that needs to land before 1.25 will be released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants