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 <a...@gmx.de>
+
+- 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: thomas.mansen...@gmail.com
@@ -13,16 +13,25 @@
         
[![Coverage](https://coveralls.io/repos/libwilliam/flask-compress/badge.svg)](https://coveralls.io/github/libwilliam/flask-compress)
         
[![License](https://img.shields.io/pypi/l/flask-compress.svg)](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: thomas.mansen...@gmail.com
@@ -13,16 +13,25 @@
         
[![Coverage](https://coveralls.io/repos/libwilliam/flask-compress/badge.svg)](https://coveralls.io/github/libwilliam/flask-compress)
         
[![License](https://img.shields.io/pypi/l/flask-compress.svg)](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 @@
 
[![Coverage](https://coveralls.io/repos/libwilliam/flask-compress/badge.svg)](https://coveralls.io/github/libwilliam/flask-compress)
 
[![License](https://img.shields.io/pypi/l/flask-compress.svg)](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
+@app.route("/test")
+@compress.compressed()
+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='thomas.mansen...@gmail.com',
-    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()

Reply via email to