Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-Flask-Compress for
openSUSE:Factory checked in at 2021-06-01 10:40:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Flask-Compress (Old)
and /work/SRC/openSUSE:Factory/.python-Flask-Compress.new.1898 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Flask-Compress"
Tue Jun 1 10:40:10 2021 rev:5 rq:896580 version:1.8.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-Flask-Compress/python-Flask-Compress.changes
2020-05-16 22:27:24.733405861 +0200
+++
/work/SRC/openSUSE:Factory/.python-Flask-Compress.new.1898/python-Flask-Compress.changes
2021-06-01 10:41:45.081251235 +0200
@@ -1,0 +2,28 @@
+Thu Nov 26 17:45:07 UTC 2020 - Arun Persaud <[email protected]>
+
+- specfile:
+ * update copyright year
+
+- update to version 1.8.0:
+ * Support ETag header as defined in RFC7232 #17
+ * Implement per-view compression #14
+
+- changes from version 1.7.0 :
+ * The following parameters to control Brotli compression are now
+ available: #10
+ + COMPRESS_BR_MODE
+ + COMPRESS_BR_LEVEL
+ + COMPRESS_BR_WINDOW
+ + COMPRESS_BR_BLOCK
+ * Add deflate support, with COMPRESS_DEFLATE_LEVEL to control
+ compression level (default is -1) #8
+ * The default quality level for Brotli is now 4, which provides
+ compression comparable to gzip at the default setting, while
+ reducing the time required versus the Brotli default of 11
+
+- changes from version 1.6.0:
+ * Support for multiple compression algorithms and quality factors #7
+ * Modified default compression settings to use Brotli when available
+ before gzip
+
+-------------------------------------------------------------------
Old:
----
Flask-Compress-1.5.0.tar.gz
New:
----
Flask-Compress-1.8.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-Flask-Compress.spec ++++++
--- /var/tmp/diff_new_pack.bG9kwZ/_old 2021-06-01 10:41:45.513251970 +0200
+++ /var/tmp/diff_new_pack.bG9kwZ/_new 2021-06-01 10:41:45.517251978 +0200
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-Flask-Compress
-Version: 1.5.0
+Version: 1.8.0
Release: 0
Summary: Compress responses in Flask apps with gzip
License: MIT
++++++ Flask-Compress-1.5.0.tar.gz -> Flask-Compress-1.8.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Flask-Compress-1.5.0/Flask_Compress.egg-info/PKG-INFO
new/Flask-Compress-1.8.0/Flask_Compress.egg-info/PKG-INFO
--- old/Flask-Compress-1.5.0/Flask_Compress.egg-info/PKG-INFO 2020-04-27
09:10:49.000000000 +0200
+++ new/Flask-Compress-1.8.0/Flask_Compress.egg-info/PKG-INFO 2020-11-03
14:25:41.000000000 +0100
@@ -1,7 +1,7 @@
Metadata-Version: 2.1
Name: Flask-Compress
-Version: 1.5.0
-Summary: Compress responses in your Flask app with gzip or brotli.
+Version: 1.8.0
+Summary: Compress responses in your Flask app with gzip, deflate or brotli.
Home-page: https://github.com/colour-science/flask-compress
Author: Thomas Mansencal
Author-email: [email protected]
@@ -13,16 +13,25 @@
[](https://coveralls.io/github/libwilliam/flask-compress)
[](https://github.com/libwilliam/flask-compress/blob/master/LICENSE.txt)
- Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip.
+ Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip, deflate or
brotli.
The preferred solution is to have a server (like
[Nginx](http://wiki.nginx.org/Main)) automatically compress the static files
for you. If you don't have that option Flask-Compress will solve the problem
for you.
## How it works
- Flask-Compress both adds the various headers required for a compressed
response and gzips the response data. This makes serving gzip compressed static
files extremely easy.
+ Flask-Compress both adds the various headers required for a compressed
response and compresses the response data.
+ This makes serving compressed static files extremely easy.
- Internally, every time a request is made the extension will check if
it matches one of the compressible MIME types and will automatically attach the
appropriate headers.
+ Internally, every time a request is made the extension will check if
it matches one of the compressible MIME types
+ and whether the client and the server use some common compression
algorithm, and will automatically attach the
+ appropriate headers.
+
+ To determine the compression algorithm, the `Accept-Encoding` request
header is inspected, respecting the
+ quality factor as described in [MDN
docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding).
+ If no requested compression algorithm is supported by the server, we
don't compress the response. If, on the other
+ hand, multiple suitable algorithms are found and are requested with
the same quality factor, we choose the first one
+ defined in the `COMPRESS_ALGORITHM` option (see below).
## Installation
@@ -48,7 +57,9 @@
## Using Flask-Compress
- Flask-Compress is incredibly simple to use. In order to start gzip'ing
your Flask application's assets, the first thing to do is let Flask-Compress
know about your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
+ ### Globally
+
+ Flask-Compress is incredibly simple to use. In order to start
compressing your Flask application's assets, the first thing to do is let
Flask-Compress know about your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
```python
from flask import Flask
@@ -72,8 +83,27 @@
return app
```
- In terms of automatically compressing your assets using gzip, passing
your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
object to the `flask_compress.Compress` object is all that needs to be done.
+ In terms of automatically compressing your assets, passing your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to
the `flask_compress.Compress` object is all that needs to be done.
+
+ ### Per-view compression
+
+ Compression is possible per view using the `@compress.compressed()`
decorator. Make sure to disable global compression first.
+ ```python
+ from flask import Flask
+ from flask_compress import Compress
+
+ app = Flask(__name__)
+ app.config["COMPRESS_REGISTER"] = False # disable default compression
of all eligible requests
+ compress = Compress()
+ compress.init_app(app)
+
+ # Compress this view specifically
+ @app.route("/test")
+ @compress.compressed()
+ def view():
+ pass
+ ```
## Options
@@ -83,11 +113,16 @@
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. |
`[`<br>`'text/html',`<br>`'text/css',`<br>`'text/xml',`<br>`'application/json',`<br>`'application/javascript'`<br>`]`
|
| `COMPRESS_LEVEL` | Specifies the gzip compression level. | `6` |
+ | `COMPRESS_BR_LEVEL` | Specifies the Brotli compression level. Ranges
from 0 to 11. | `4` |
+ | `COMPRESS_BR_MODE` | For Brotli, the compression mode. The options
are 0, 1, or 2. These correspond to "generic", "text" (for UTF-8 input), and
"font" (for WOFF 2.0). | `0` |
+ | `COMPRESS_BR_WINDOW` | For Brotli, this specifies the base-2
logarithm of the sliding window size. Ranges from 10 to 24. | `22` |
+ | `COMPRESS_BR_BLOCK` | For Brotli, this provides the base-2 logarithm
of the maximum input block size. If zero is provided, value will be determined
based on the quality. Ranges from 16 to 24. | `0` |
+ | `COMPRESS_DEFLATE_LEVEL` | Specifies the deflate compression level.
| `-1` |
| `COMPRESS_MIN_SIZE` | Specifies the minimum file size threshold for
compressing files. | `500` |
| `COMPRESS_CACHE_KEY` | Specifies the cache key method for
lookup/storage of response data. | `None` |
| `COMPRESS_CACHE_BACKEND` | Specified the backend for storing the
cached response data. | `None` |
| `COMPRESS_REGISTER` | Specifies if compression should be
automatically registered. | `True` |
- | `COMPRESS_ALGORITHM` | Compression algorithm used: `gzip` or `br`. |
`gzip` |
+ | `COMPRESS_ALGORITHM` | Supported compression algorithms. | `['br',
'gzip', 'deflate']` |
Platform: any
Classifier: Environment :: Web Environment
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Flask-Compress-1.5.0/PKG-INFO
new/Flask-Compress-1.8.0/PKG-INFO
--- old/Flask-Compress-1.5.0/PKG-INFO 2020-04-27 09:10:49.000000000 +0200
+++ new/Flask-Compress-1.8.0/PKG-INFO 2020-11-03 14:25:41.266460700 +0100
@@ -1,7 +1,7 @@
Metadata-Version: 2.1
Name: Flask-Compress
-Version: 1.5.0
-Summary: Compress responses in your Flask app with gzip or brotli.
+Version: 1.8.0
+Summary: Compress responses in your Flask app with gzip, deflate or brotli.
Home-page: https://github.com/colour-science/flask-compress
Author: Thomas Mansencal
Author-email: [email protected]
@@ -13,16 +13,25 @@
[](https://coveralls.io/github/libwilliam/flask-compress)
[](https://github.com/libwilliam/flask-compress/blob/master/LICENSE.txt)
- Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip.
+ Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip, deflate or
brotli.
The preferred solution is to have a server (like
[Nginx](http://wiki.nginx.org/Main)) automatically compress the static files
for you. If you don't have that option Flask-Compress will solve the problem
for you.
## How it works
- Flask-Compress both adds the various headers required for a compressed
response and gzips the response data. This makes serving gzip compressed static
files extremely easy.
+ Flask-Compress both adds the various headers required for a compressed
response and compresses the response data.
+ This makes serving compressed static files extremely easy.
- Internally, every time a request is made the extension will check if
it matches one of the compressible MIME types and will automatically attach the
appropriate headers.
+ Internally, every time a request is made the extension will check if
it matches one of the compressible MIME types
+ and whether the client and the server use some common compression
algorithm, and will automatically attach the
+ appropriate headers.
+
+ To determine the compression algorithm, the `Accept-Encoding` request
header is inspected, respecting the
+ quality factor as described in [MDN
docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding).
+ If no requested compression algorithm is supported by the server, we
don't compress the response. If, on the other
+ hand, multiple suitable algorithms are found and are requested with
the same quality factor, we choose the first one
+ defined in the `COMPRESS_ALGORITHM` option (see below).
## Installation
@@ -48,7 +57,9 @@
## Using Flask-Compress
- Flask-Compress is incredibly simple to use. In order to start gzip'ing
your Flask application's assets, the first thing to do is let Flask-Compress
know about your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
+ ### Globally
+
+ Flask-Compress is incredibly simple to use. In order to start
compressing your Flask application's assets, the first thing to do is let
Flask-Compress know about your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
```python
from flask import Flask
@@ -72,8 +83,27 @@
return app
```
- In terms of automatically compressing your assets using gzip, passing
your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
object to the `flask_compress.Compress` object is all that needs to be done.
+ In terms of automatically compressing your assets, passing your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to
the `flask_compress.Compress` object is all that needs to be done.
+
+ ### Per-view compression
+
+ Compression is possible per view using the `@compress.compressed()`
decorator. Make sure to disable global compression first.
+ ```python
+ from flask import Flask
+ from flask_compress import Compress
+
+ app = Flask(__name__)
+ app.config["COMPRESS_REGISTER"] = False # disable default compression
of all eligible requests
+ compress = Compress()
+ compress.init_app(app)
+
+ # Compress this view specifically
+ @app.route("/test")
+ @compress.compressed()
+ def view():
+ pass
+ ```
## Options
@@ -83,11 +113,16 @@
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. |
`[`<br>`'text/html',`<br>`'text/css',`<br>`'text/xml',`<br>`'application/json',`<br>`'application/javascript'`<br>`]`
|
| `COMPRESS_LEVEL` | Specifies the gzip compression level. | `6` |
+ | `COMPRESS_BR_LEVEL` | Specifies the Brotli compression level. Ranges
from 0 to 11. | `4` |
+ | `COMPRESS_BR_MODE` | For Brotli, the compression mode. The options
are 0, 1, or 2. These correspond to "generic", "text" (for UTF-8 input), and
"font" (for WOFF 2.0). | `0` |
+ | `COMPRESS_BR_WINDOW` | For Brotli, this specifies the base-2
logarithm of the sliding window size. Ranges from 10 to 24. | `22` |
+ | `COMPRESS_BR_BLOCK` | For Brotli, this provides the base-2 logarithm
of the maximum input block size. If zero is provided, value will be determined
based on the quality. Ranges from 16 to 24. | `0` |
+ | `COMPRESS_DEFLATE_LEVEL` | Specifies the deflate compression level.
| `-1` |
| `COMPRESS_MIN_SIZE` | Specifies the minimum file size threshold for
compressing files. | `500` |
| `COMPRESS_CACHE_KEY` | Specifies the cache key method for
lookup/storage of response data. | `None` |
| `COMPRESS_CACHE_BACKEND` | Specified the backend for storing the
cached response data. | `None` |
| `COMPRESS_REGISTER` | Specifies if compression should be
automatically registered. | `True` |
- | `COMPRESS_ALGORITHM` | Compression algorithm used: `gzip` or `br`. |
`gzip` |
+ | `COMPRESS_ALGORITHM` | Supported compression algorithms. | `['br',
'gzip', 'deflate']` |
Platform: any
Classifier: Environment :: Web Environment
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Flask-Compress-1.5.0/README.md
new/Flask-Compress-1.8.0/README.md
--- old/Flask-Compress-1.5.0/README.md 2020-04-21 10:36:48.000000000 +0200
+++ new/Flask-Compress-1.8.0/README.md 2020-11-02 09:38:17.000000000 +0100
@@ -5,16 +5,25 @@
[](https://coveralls.io/github/libwilliam/flask-compress)
[](https://github.com/libwilliam/flask-compress/blob/master/LICENSE.txt)
-Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip.
+Flask-Compress allows you to easily compress your
[Flask](http://flask.pocoo.org/) application's responses with gzip, deflate or
brotli.
The preferred solution is to have a server (like
[Nginx](http://wiki.nginx.org/Main)) automatically compress the static files
for you. If you don't have that option Flask-Compress will solve the problem
for you.
## How it works
-Flask-Compress both adds the various headers required for a compressed
response and gzips the response data. This makes serving gzip compressed static
files extremely easy.
+Flask-Compress both adds the various headers required for a compressed
response and compresses the response data.
+This makes serving compressed static files extremely easy.
-Internally, every time a request is made the extension will check if it
matches one of the compressible MIME types and will automatically attach the
appropriate headers.
+Internally, every time a request is made the extension will check if it
matches one of the compressible MIME types
+and whether the client and the server use some common compression algorithm,
and will automatically attach the
+appropriate headers.
+
+To determine the compression algorithm, the `Accept-Encoding` request header
is inspected, respecting the
+quality factor as described in [MDN
docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding).
+If no requested compression algorithm is supported by the server, we don't
compress the response. If, on the other
+hand, multiple suitable algorithms are found and are requested with the same
quality factor, we choose the first one
+defined in the `COMPRESS_ALGORITHM` option (see below).
## Installation
@@ -40,7 +49,9 @@
## Using Flask-Compress
-Flask-Compress is incredibly simple to use. In order to start gzip'ing your
Flask application's assets, the first thing to do is let Flask-Compress know
about your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
+### Globally
+
+Flask-Compress is incredibly simple to use. In order to start compressing your
Flask application's assets, the first thing to do is let Flask-Compress know
about your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask)
application object.
```python
from flask import Flask
@@ -64,8 +75,27 @@
return app
```
-In terms of automatically compressing your assets using gzip, passing your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to
the `flask_compress.Compress` object is all that needs to be done.
+In terms of automatically compressing your assets, passing your
[`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to
the `flask_compress.Compress` object is all that needs to be done.
+
+### Per-view compression
+
+Compression is possible per view using the `@compress.compressed()` decorator.
Make sure to disable global compression first.
+```python
+from flask import Flask
+from flask_compress import Compress
+
+app = Flask(__name__)
+app.config["COMPRESS_REGISTER"] = False # disable default compression of all
eligible requests
+compress = Compress()
+compress.init_app(app)
+
+# Compress this view specifically
[email protected]("/test")
[email protected]()
+def view():
+ pass
+```
## Options
@@ -75,8 +105,13 @@
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. |
`[`<br>`'text/html',`<br>`'text/css',`<br>`'text/xml',`<br>`'application/json',`<br>`'application/javascript'`<br>`]`
|
| `COMPRESS_LEVEL` | Specifies the gzip compression level. | `6` |
+| `COMPRESS_BR_LEVEL` | Specifies the Brotli compression level. Ranges from 0
to 11. | `4` |
+| `COMPRESS_BR_MODE` | For Brotli, the compression mode. The options are 0, 1,
or 2. These correspond to "generic", "text" (for UTF-8 input), and "font" (for
WOFF 2.0). | `0` |
+| `COMPRESS_BR_WINDOW` | For Brotli, this specifies the base-2 logarithm of
the sliding window size. Ranges from 10 to 24. | `22` |
+| `COMPRESS_BR_BLOCK` | For Brotli, this provides the base-2 logarithm of the
maximum input block size. If zero is provided, value will be determined based
on the quality. Ranges from 16 to 24. | `0` |
+| `COMPRESS_DEFLATE_LEVEL` | Specifies the deflate compression level. | `-1` |
| `COMPRESS_MIN_SIZE` | Specifies the minimum file size threshold for
compressing files. | `500` |
| `COMPRESS_CACHE_KEY` | Specifies the cache key method for lookup/storage of
response data. | `None` |
| `COMPRESS_CACHE_BACKEND` | Specified the backend for storing the cached
response data. | `None` |
| `COMPRESS_REGISTER` | Specifies if compression should be automatically
registered. | `True` |
-| `COMPRESS_ALGORITHM` | Compression algorithm used: `gzip` or `br`. | `gzip` |
+| `COMPRESS_ALGORITHM` | Supported compression algorithms. | `['br', 'gzip',
'deflate']` |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Flask-Compress-1.5.0/flask_compress.py
new/Flask-Compress-1.8.0/flask_compress.py
--- old/Flask-Compress-1.5.0/flask_compress.py 2020-04-21 09:38:27.000000000
+0200
+++ new/Flask-Compress-1.8.0/flask_compress.py 2020-11-03 09:34:40.000000000
+0100
@@ -4,11 +4,15 @@
# License: The MIT License (MIT)
import sys
+import functools
from gzip import GzipFile
+import zlib
from io import BytesIO
+from collections import defaultdict
+
import brotli
-from flask import request, current_app
+from flask import request, after_this_request, current_app
if sys.version_info[:2] == (2, 6):
@@ -64,11 +68,16 @@
'application/json',
'application/javascript']),
('COMPRESS_LEVEL', 6),
+ ('COMPRESS_BR_LEVEL', 4),
+ ('COMPRESS_BR_MODE', 0),
+ ('COMPRESS_BR_WINDOW', 22),
+ ('COMPRESS_BR_BLOCK', 0),
+ ('COMPRESS_DEFLATE_LEVEL', -1),
('COMPRESS_MIN_SIZE', 500),
('COMPRESS_CACHE_KEY', None),
('COMPRESS_CACHE_BACKEND', None),
('COMPRESS_REGISTER', True),
- ('COMPRESS_ALGORITHM', 'gzip'),
+ ('COMPRESS_ALGORITHM', ['br', 'gzip', 'deflate']),
]
for k, v in defaults:
@@ -78,55 +87,147 @@
self.cache = backend() if backend else None
self.cache_key = app.config['COMPRESS_CACHE_KEY']
+ algo = app.config['COMPRESS_ALGORITHM']
+ if isinstance(algo, str):
+ self.enabled_algorithms = [i.strip() for i in algo.split(',')]
+ else:
+ self.enabled_algorithms = algo
+
if (app.config['COMPRESS_REGISTER'] and
app.config['COMPRESS_MIMETYPES']):
app.after_request(self.after_request)
+ def _choose_compress_algorithm(self, accept_encoding_header):
+ """
+ Determine which compression algorithm we're going to use based on the
+ client request. The `Accept-Encoding` header may list one or more
desired
+ algorithms, together with a "quality factor" for each one (higher
quality
+ means the client prefers that algorithm more).
+
+ :param accept_encoding_header: Content of the `Accept-Encoding` header
+ :return: name of a compression algorithm (`gzip`, `deflate`, `br`) or
`None` if
+ the client and server don't agree on any.
+ """
+ # Map quality factors to requested algorithm names.
+ algos_by_quality = defaultdict(set)
+
+ # A flag denoting that client requested using any (`*`) algorithm,
+ # in case a specific one is not supported by the server
+ fallback_to_any = False
+
+ for part in accept_encoding_header.lower().split(','):
+ part = part.strip()
+ quality = 1.0
+
+ if ';q=' in part:
+ # If the client associated a quality factor with an algorithm,
+ # try to parse it. We could do the matching using a regex, but
+ # the format is so simple that it would be overkill.
+ algo = part.split(';')[0].strip()
+ try:
+ quality = float(part.split('=')[1].strip())
+ except ValueError:
+ pass
+ else:
+ # Otherwise, use the default quality
+ algo = part
+
+ algos_by_quality[quality].add(algo)
+ fallback_to_any = fallback_to_any or (algo == '*')
+
+ # Choose the algorithm with the highest quality factor that the server
supports.
+ #
+ # If there are multiple equally good options, choose the first
supported algorithm
+ # from server configuration.
+ #
+ # If the server doesn't support any algorithm that the client
requested but
+ # there's a special wildcard algorithm request (`*`), choose the first
supported
+ # algorithm.
+ server_algo_set = set(self.enabled_algorithms)
+ for _, requested_algo_set in sorted(algos_by_quality.items(),
reverse=True):
+ viable_algos = server_algo_set & requested_algo_set
+ if len(viable_algos) == 1:
+ return viable_algos.pop()
+ elif len(viable_algos) > 1:
+ for server_algo in self.enabled_algorithms:
+ if server_algo in viable_algos:
+ return server_algo
+ else:
+ if fallback_to_any:
+ return self.enabled_algorithms[0]
+
+ return None
+
def after_request(self, response):
app = self.app or current_app
+
accept_encoding = request.headers.get('Accept-Encoding', '')
+ chosen_algorithm = self._choose_compress_algorithm(accept_encoding)
- if (response.mimetype not in app.config['COMPRESS_MIMETYPES'] or
- ('gzip' not in accept_encoding.lower() and
app.config['COMPRESS_ALGORITHM'] == 'gzip') or
- ('br' not in accept_encoding.lower() and
app.config['COMPRESS_ALGORITHM'] == 'br') or
- not 200 <= response.status_code < 300 or
+ if (chosen_algorithm is None or
+ response.mimetype not in app.config["COMPRESS_MIMETYPES"] or
+ response.status_code < 200 or
+ response.status_code >= 300 or
+ "Content-Encoding" in response.headers or
(response.content_length is not None and
- response.content_length < app.config['COMPRESS_MIN_SIZE']) or
- 'Content-Encoding' in response.headers):
+ response.content_length < app.config["COMPRESS_MIN_SIZE"])):
return response
response.direct_passthrough = False
- if self.cache:
+ if self.cache is not None:
key = self.cache_key(request)
compressed_content = self.cache.get(key)
if compressed_content is None:
- compressed_content = self.compress(app, response)
+ compressed_content = self.compress(app, response,
chosen_algorithm)
self.cache.set(key, compressed_content)
else:
- compressed_content = self.compress(app, response)
+ compressed_content = self.compress(app, response, chosen_algorithm)
response.set_data(compressed_content)
- response.headers['Content-Encoding'] = app.config['COMPRESS_ALGORITHM']
+ response.headers['Content-Encoding'] = chosen_algorithm
response.headers['Content-Length'] = response.content_length
+ # "123456789" => "123456789:gzip" - A strong ETag validator
+ # W/"123456789" => W/"123456789:gzip" - A weak ETag validator
+ etag = response.headers.get('ETag')
+ if etag:
+ response.headers['ETag'] = '{0}:{1}"'.format(etag[:-1],
chosen_algorithm)
+
vary = response.headers.get('Vary')
- if vary:
- if 'accept-encoding' not in vary.lower():
- response.headers['Vary'] = '{}, Accept-Encoding'.format(vary)
- else:
+ if not vary:
response.headers['Vary'] = 'Accept-Encoding'
+ elif 'accept-encoding' not in vary.lower():
+ response.headers['Vary'] = '{}, Accept-Encoding'.format(vary)
return response
- def compress(self, app, response):
- if app.config['COMPRESS_ALGORITHM'] == 'gzip':
+ def compressed(self):
+ def decorator(f):
+ @functools.wraps(f)
+ def decorated_function(*args, **kwargs):
+ @after_this_request
+ def compressor(response):
+ return self.after_request(response)
+ return f(*args, **kwargs)
+ return decorated_function
+ return decorator
+
+ def compress(self, app, response, algorithm):
+ if algorithm == 'gzip':
gzip_buffer = BytesIO()
with GzipFile(mode='wb',
compresslevel=app.config['COMPRESS_LEVEL'],
fileobj=gzip_buffer) as gzip_file:
gzip_file.write(response.get_data())
return gzip_buffer.getvalue()
- elif app.config['COMPRESS_ALGORITHM'] == 'br':
- return brotli.compress(response.get_data())
+ elif algorithm == 'deflate':
+ return zlib.compress(response.get_data(),
+ app.config['COMPRESS_DEFLATE_LEVEL'])
+ elif algorithm == 'br':
+ return brotli.compress(response.get_data(),
+ mode=app.config['COMPRESS_BR_MODE'],
+ quality=app.config['COMPRESS_BR_LEVEL'],
+ lgwin=app.config['COMPRESS_BR_WINDOW'],
+ lgblock=app.config['COMPRESS_BR_BLOCK'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Flask-Compress-1.5.0/setup.py
new/Flask-Compress-1.8.0/setup.py
--- old/Flask-Compress-1.5.0/setup.py 2020-04-27 09:06:00.000000000 +0200
+++ new/Flask-Compress-1.8.0/setup.py 2020-11-03 14:20:11.000000000 +0100
@@ -5,12 +5,12 @@
setuptools.setup(
name='Flask-Compress',
- version='1.5.0',
+ version='1.8.0',
url='https://github.com/colour-science/flask-compress',
license='MIT',
author='Thomas Mansencal',
author_email='[email protected]',
- description='Compress responses in your Flask app with gzip or brotli.',
+ description='Compress responses in your Flask app with gzip, deflate or
brotli.',
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
py_modules=['flask_compress'],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Flask-Compress-1.5.0/tests/test_flask_compress.py
new/Flask-Compress-1.8.0/tests/test_flask_compress.py
--- old/Flask-Compress-1.5.0/tests/test_flask_compress.py 2020-04-21
09:38:27.000000000 +0200
+++ new/Flask-Compress-1.8.0/tests/test_flask_compress.py 2020-11-02
09:38:17.000000000 +0100
@@ -29,8 +29,27 @@
def test_algorithm_default(self):
""" Tests COMPRESS_ALGORITHM default value is correctly set. """
- self.assertEqual(self.app.config['COMPRESS_ALGORITHM'], 'gzip')
+ self.assertEqual(self.app.config['COMPRESS_ALGORITHM'], ['br', 'gzip',
'deflate'])
+ def test_default_deflate_settings(self):
+ """ Tests COMPRESS_DELATE_LEVEL default value is correctly set. """
+ self.assertEqual(self.app.config['COMPRESS_DEFLATE_LEVEL'], -1)
+
+ def test_mode_default(self):
+ """ Tests COMPRESS_BR_MODE default value is correctly set. """
+ self.assertEqual(self.app.config['COMPRESS_BR_MODE'], 0)
+
+ def test_quality_level_default(self):
+ """ Tests COMPRESS_BR_LEVEL default value is correctly set. """
+ self.assertEqual(self.app.config['COMPRESS_BR_LEVEL'], 4)
+
+ def test_window_size_default(self):
+ """ Tests COMPRESS_BR_WINDOW default value is correctly set. """
+ self.assertEqual(self.app.config['COMPRESS_BR_WINDOW'], 22)
+
+ def test_block_size_default(self):
+ """ Tests COMPRESS_BR_BLOCK default value is correctly set. """
+ self.assertEqual(self.app.config['COMPRESS_BR_BLOCK'], 0)
class InitTests(unittest.TestCase):
def setUp(self):
@@ -85,18 +104,6 @@
response = client.options('/large/', headers=headers)
self.assertEqual(response.status_code, 200)
- def test_compress_level(self):
- """ Tests COMPRESS_LEVEL correctly affects response data. """
- self.app.config['COMPRESS_LEVEL'] = 1
- response = self.client_get('/large/')
- response1_size = len(response.data)
-
- self.app.config['COMPRESS_LEVEL'] = 6
- response = self.client_get('/large/')
- response6_size = len(response.data)
-
- self.assertNotEqual(response1_size, response6_size)
-
def test_compress_min_size(self):
""" Tests COMPRESS_MIN_SIZE correctly affects response data. """
response = self.client_get('/small/')
@@ -116,5 +123,210 @@
response = client.options('/small/', headers=headers)
self.assertEqual(response.status_code, 200)
+ def test_gzip_compression_level(self):
+ """ Tests COMPRESS_LEVEL correctly affects response data. """
+ self.app.config['COMPRESS_LEVEL'] = 1
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding', 'gzip')])
+ response1_size = len(response.data)
+
+ self.app.config['COMPRESS_LEVEL'] = 6
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding', 'gzip')])
+ response6_size = len(response.data)
+
+ self.assertNotEqual(response1_size, response6_size)
+
+ def test_br_compression_level(self):
+ """ Tests that COMPRESS_BR_LEVEL correctly affects response data. """
+ self.app.config['COMPRESS_BR_LEVEL'] = 4
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding', 'br')])
+ response4_size = len(response.data)
+
+ self.app.config['COMPRESS_BR_LEVEL'] = 11
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding', 'br')])
+ response11_size = len(response.data)
+
+ self.assertNotEqual(response4_size, response11_size)
+
+ def test_deflate_compression_level(self):
+ """ Tests COMPRESS_DELATE_LEVEL correctly affects response data. """
+ self.app.config['COMPRESS_DEFLATE_LEVEL'] = -1
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding',
'deflate')])
+ response_size = len(response.data)
+
+ self.app.config['COMPRESS_DEFLATE_LEVEL'] = 1
+ client = self.app.test_client()
+ response = client.get('/large/', headers=[('Accept-Encoding',
'deflate')])
+ response1_size = len(response.data)
+
+ self.assertNotEqual(response_size, response1_size)
+
+
+class CompressionAlgoTests(unittest.TestCase):
+ """
+ Test different scenarios for compression algorithm negotiation between
+ client and server. Please note that algorithm names (even the "supported"
+ ones) in these tests **do not** indicate that all of these are actually
+ supported by this extension.
+ """
+ def setUp(self):
+ super(CompressionAlgoTests, self).setUp()
+
+ # Create the app here but don't call `Compress()` on it just yet; we
need
+ # to be able to modify the settings in various tests. Calling
`Compress(self.app)`
+ # twice would result in two `@after_request` handlers, which would be
bad.
+ self.app = Flask(__name__)
+ self.app.testing = True
+
+ small_path = os.path.join(os.getcwd(), 'tests', 'templates',
'small.html')
+ self.small_size = os.path.getsize(small_path) - 1
+
+ @self.app.route('/small/')
+ def small():
+ return render_template('small.html')
+
+ def test_setting_compress_algorithm_simple_string(self):
+ """ Test that a single entry in `COMPRESS_ALGORITHM` still works for
backwards compatibility """
+ self.app.config['COMPRESS_ALGORITHM'] = 'gzip'
+ c = Compress(self.app)
+ self.assertListEqual(c.enabled_algorithms, ['gzip'])
+
+ def test_setting_compress_algorithm_cs_string(self):
+ """ Test that `COMPRESS_ALGORITHM` can be a comma-separated string """
+ self.app.config['COMPRESS_ALGORITHM'] = 'gzip, br, zstd'
+ c = Compress(self.app)
+ self.assertListEqual(c.enabled_algorithms, ['gzip', 'br', 'zstd'])
+
+ def test_setting_compress_algorithm_list(self):
+ """ Test that `COMPRESS_ALGORITHM` can be a list of strings """
+ self.app.config['COMPRESS_ALGORITHM'] = ['gzip', 'br', 'deflate']
+ c = Compress(self.app)
+ self.assertListEqual(c.enabled_algorithms, ['gzip', 'br', 'deflate'])
+
+ def test_one_algo_supported(self):
+ """ Tests requesting a single supported compression algorithm """
+ accept_encoding = 'gzip'
+ self.app.config['COMPRESS_ALGORITHM'] = ['br', 'gzip']
+ c = Compress(self.app)
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'gzip')
+
+ def test_one_algo_unsupported(self):
+ """ Tests requesting single unsupported compression algorithm """
+ accept_encoding = 'some-alien-algorithm'
+ self.app.config['COMPRESS_ALGORITHM'] = ['br', 'gzip']
+ c = Compress(self.app)
+ self.assertIsNone(c._choose_compress_algorithm(accept_encoding))
+
+ def test_multiple_algos_supported(self):
+ """ Tests requesting multiple supported compression algorithms """
+ accept_encoding = 'br, gzip, zstd'
+ self.app.config['COMPRESS_ALGORITHM'] = ['zstd', 'br', 'gzip']
+ c = Compress(self.app)
+ # When the decision is tied, we expect to see the first
server-configured algorithm
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'zstd')
+
+ def test_multiple_algos_unsupported(self):
+ """ Tests requesting multiple unsupported compression algorithms """
+ accept_encoding = 'future-algo, alien-algo, forbidden-algo'
+ self.app.config['COMPRESS_ALGORITHM'] = ['zstd', 'br', 'gzip']
+ c = Compress(self.app)
+ self.assertIsNone(c._choose_compress_algorithm(accept_encoding))
+
+ def test_multiple_algos_with_wildcard(self):
+ """ Tests requesting multiple unsupported compression algorithms and a
wildcard """
+ accept_encoding = 'future-algo, alien-algo, forbidden-algo, *'
+ self.app.config['COMPRESS_ALGORITHM'] = ['zstd', 'br', 'gzip']
+ c = Compress(self.app)
+ # We expect to see the first server-configured algorithm
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'zstd')
+
+ def test_multiple_algos_with_different_quality(self):
+ """ Tests requesting multiple supported compression algorithms with
different q-factors """
+ accept_encoding = 'zstd;q=0.8, br;q=0.9, gzip;q=0.5'
+ self.app.config['COMPRESS_ALGORITHM'] = ['zstd', 'br', 'gzip']
+ c = Compress(self.app)
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'br')
+
+ def test_multiple_algos_with_equal_quality(self):
+ """ Tests requesting multiple supported compression algorithms with
equal q-factors """
+ accept_encoding = 'zstd;q=0.5, br;q=0.5, gzip;q=0.5'
+ self.app.config['COMPRESS_ALGORITHM'] = ['gzip', 'br', 'zstd']
+ c = Compress(self.app)
+ # We expect to see the first server-configured algorithm
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'gzip')
+
+ def test_default_quality_is_1(self):
+ """ Tests that when making mixed-quality requests, the default
q-factor is 1.0 """
+ accept_encoding = 'deflate, br;q=0.999, gzip;q=0.5'
+ self.app.config['COMPRESS_ALGORITHM'] = ['gzip', 'br', 'deflate']
+ c = Compress(self.app)
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding),
'deflate')
+
+ def test_default_wildcard_quality_is_0(self):
+ """ Tests that a wildcard has a default q-factor of 0.0 """
+ accept_encoding = 'br;q=0.001, *'
+ self.app.config['COMPRESS_ALGORITHM'] = ['gzip', 'br', 'deflate']
+ c = Compress(self.app)
+ self.assertEqual(c._choose_compress_algorithm(accept_encoding), 'br')
+
+ def test_content_encoding_is_correct(self):
+ """ Test that the `Content-Encoding` header matches the compression
algorithm """
+ self.app.config['COMPRESS_ALGORITHM'] = ['br', 'gzip', 'deflate']
+ Compress(self.app)
+
+ headers_gzip = [('Accept-Encoding', 'gzip')]
+ client = self.app.test_client()
+ response_gzip = client.options('/small/', headers=headers_gzip)
+ self.assertIn('Content-Encoding', response_gzip.headers)
+ self.assertEqual(response_gzip.headers.get('Content-Encoding'), 'gzip')
+
+ headers_br = [('Accept-Encoding', 'br')]
+ client = self.app.test_client()
+ response_br = client.options('/small/', headers=headers_br)
+ self.assertIn('Content-Encoding', response_br.headers)
+ self.assertEqual(response_br.headers.get('Content-Encoding'), 'br')
+
+ headers_deflate = [('Accept-Encoding', 'deflate')]
+ client = self.app.test_client()
+ response_deflate = client.options('/small/', headers=headers_deflate)
+ self.assertIn('Content-Encoding', response_deflate.headers)
+ self.assertEqual(response_deflate.headers.get('Content-Encoding'),
'deflate')
+
+
+class CompressionPerViewTests(unittest.TestCase):
+ def setUp(self):
+ self.app = Flask(__name__)
+ self.app.testing = True
+ self.app.config["COMPRESS_REGISTER"] = False
+ compress = Compress()
+ compress.init_app(self.app)
+
+ @self.app.route('/route1/')
+ def view_1():
+ return render_template('large.html')
+
+ @self.app.route('/route2/')
+ @compress.compressed()
+ def view_2():
+ return render_template('large.html')
+
+ def test_compression(self):
+ client = self.app.test_client()
+ headers = [('Accept-Encoding', 'deflate')]
+
+ response = client.get('/route1/', headers=headers)
+ self.assertEqual(response.status_code, 200)
+ self.assertNotIn('Content-Encoding', response.headers)
+
+ response = client.get('/route2/', headers=headers)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('Content-Encoding', response.headers)
+ self.assertEqual(response.headers.get('Content-Encoding'), 'deflate')
+
+
if __name__ == '__main__':
unittest.main()