././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4708102
flask_compress-1.25/ 0000755 0001751 0001751 00000000000 15252212527 014125 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4691164
flask_compress-1.25/.github/ 0000755 0001751 0001751 00000000000 15252212527 015465 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465936.8397255
flask_compress-1.25/.github/dependabot.yaml 0000644 0001751 0001751 00000000273 15252212521 020452 0 ustar 00runner runner version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
github-actions:
patterns:
- "*"
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4691834
flask_compress-1.25/.github/workflows/ 0000755 0001751 0001751 00000000000 15252212527 017522 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/.github/workflows/ci.yaml 0000644 0001751 0001751 00000006310 15252212521 020773 0 ustar 00runner runner # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
pull_request:
push:
branches:
- master
tags:
- "v*"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
- '3.14'
- '3.15'
- 'pypy-3.11'
os: [ubuntu-latest, windows-latest, macos-latest]
name: ${{ matrix.os }} - ${{ matrix.python-version }}
steps:
- name: Set git to not change line endings
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: pip install tox
- name: Run tox
# Run tox using the version of Python in `PATH`
run: tox -e py,coverage_report-ci
dist:
runs-on: ubuntu-latest
needs: [test]
name: Build Python packages
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade wheel setuptools build
- name: Build package
run: python -m build -s -w -o dist/
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist
dist_check:
runs-on: ubuntu-latest
needs: [dist]
name: Twine check
steps:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install dependencies
run: pip install twine
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist
- run: twine check dist/*
dist_upload:
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
needs: [dist_check]
name: PyPI upload
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/.gitignore 0000644 0001751 0001751 00000000516 15252212521 016111 0 ustar 00runner runner *~
# vim
*.sw[klmnop]
# python
*.py[co~]
.python-version
# Ignoring build dir
/build
/dist
/*.egg-info/
# Virtual environments
venv*
.env*
# Coverage reports
/cover/
/.coverage*
/htmlcov
# Ignoring profiling reports
*.prof
*.lprof
# Tox and pytest
/.tox
/.cache
/.pytest_cache
# Generated by setuptools_scm
flask_compress/_version.py
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/.pre-commit-config.yaml 0000644 0001751 0001751 00000003411 15252212521 020377 0 ustar 00runner runner ci:
autoupdate_schedule: "quarterly"
default_language_version:
python: "python3.12"
exclude: "tests/templates/"
repos:
- repo: "meta"
hooks:
- id: "check-hooks-apply"
- id: "check-useless-excludes"
- repo: "https://github.com/pre-commit/pre-commit-hooks"
rev: "3e8a8703264a2f4a69428a0aa4dcb512790b2c8c" # frozen: v6.0.0
hooks:
- id: "check-added-large-files"
- id: "check-merge-conflict"
- id: "check-yaml"
- id: "end-of-file-fixer"
- id: "mixed-line-ending"
args:
- "--fix=lf"
- id: "trailing-whitespace"
- repo: "https://github.com/asottile/pyupgrade"
rev: "75992aaa40730136014f34227e0135f63fc951b4" # frozen: v3.21.2
hooks:
- id: "pyupgrade"
name: "Enforce Python 3.9+ idioms"
args:
- "--py39-plus"
- repo: "https://github.com/psf/black-pre-commit-mirror"
rev: "4160603246a6b365d4a2af661c6d71b0a0f50478" # frozen: 26.5.1
hooks:
- id: "black"
- repo: "https://github.com/pycqa/flake8"
rev: "c48217e1fc006c2dddd14df54e83b67da15de5cd" # frozen: 7.3.0
hooks:
- id: "flake8"
additional_dependencies:
- "flake8-bugbear==24.8.19"
- repo: "https://github.com/pre-commit/mirrors-mypy"
rev: "41e691678310dfd3833f7ab4e180ddb014310356" # frozen: v2.3.0
hooks:
- id: "mypy"
additional_dependencies:
- "flask>=3.0"
- repo: "https://github.com/python-jsonschema/check-jsonschema"
rev: "6b63472e72e1a91ed8a2f6d483790dfb644fa1d3" # frozen: 0.37.4
hooks:
- id: "check-dependabot"
- id: "check-github-workflows"
- repo: "https://github.com/rhysd/actionlint"
rev: "914e7df21a07ef503a81201c76d2b11c789d3fca" # frozen: v1.7.12
hooks:
- id: "actionlint"
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/CHANGELOG.md 0000644 0001751 0001751 00000015434 15252212521 015737 0 ustar 00runner runner # Changelog
All notable changes to `flask-compress` will be documented in this file.
## 1.25 (2026-09-15)
- support, and test against, Python 3.15
- return an empty `204 No Content` response when the Flask `after_request` hook receives no response, [#83](https://github.com/colour-science/flask-compress/pull/83) by @mmdbalkhi
## 1.24 (2026-03-31)
- add mypy annotations and mark it as checkable, [#76](https://github.com/colour-science/flask-compress/pull/76) by @orborde
- enable trusted publishing, allowing GitHub Actions to publish releases to PyPI without long lived credentials, [#78](https://github.com/colour-science/flask-compress/issues/78) by @alexprengere
## 1.23 (2025-11-06)
- add `gzip` to supported algorithms for streaming responses, but not in the `COMPRESS_ALGORITHM_STREAMING` default values, so users have to explicitly enable it
- add Windows and MacOS to the list of platforms tested on GitHub Actions
## 1.22 (2025-11-04)
- fix possible `RuntimeError` when applying compression to a streaming endpoint
- no longer apply `make_conditional` to streaming responses, as it had the effect of buffering the entire response, which defeats the purpose of streaming responses, unless the `request.endpoint` is in a new `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` config option, which defaults to `["static"]`
> As serving static content with Flask is using *streaming* responses, we wanted to preserve the correct handling of conditional requests (i.e. 304 Not Modified responses) for static content, while avoiding buffering the entire response when compressing streaming responses for other endpoints
## 1.21 (2025-11-04, yanked)
- compressing *streaming* responses is now supported:
- the *previous* behavior was:
- if `COMPRESS_STREAMS` was `True` (the default), streaming responses were compressed in *1 sitting* using `response.get_data()`, meaning streaming responses were compressed but *not streamed*
- if `COMPRESS_STREAMS` was `False`, streaming responses were not compressed at all
- the *new* behavior is:
- if `COMPRESS_STREAMS` is `True`, streaming responses are compressed chunk-by-chunk using `response.iter_encoded()`, which means that streaming responses are compressed and streamed
- if `COMPRESS_STREAMS` is `False`, streaming responses are not compressed at all, as before.
- we have a new `COMPRESS_ALGORITHM_STREAMING` config option to specify the compression algorithm to use for streaming responses, which defaults to `["zstd", "br", "deflate"]`, as `"gzip"` is not suitable for streaming compression.
- ⚠� serving static content with Flask is actually using *streaming* responses, so if you enable streaming compression, static content will be compressed chunk-by-chunk as well, which may not be what you want.
## 1.20 (2025-10-20)
- the new `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` config option is now `True` by default
## 1.19 (2025-10-20)
- Use `backports.zstd` instead of `pyzstd`, [#63](https://github.com/colour-science/flask-compress/pull/63) by @alexprengere
- Add new behaviours to improve Etag compatibility, [#67](https://github.com/colour-science/flask-compress/pull/67), by @jlopex
- weak etags are no longer modified by flask-compress
- add new `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` config option to call `response.make_conditional(request)` after compression, this ensures that 304 Not Modified responses are correctly generated when the client sends conditional requests with `If-None-Match` (defaults to `False`)
## 1.18 (2025-07-11)
- Support, and test against, Python 3.14, see [#61](https://github.com/colour-science/flask-compress/pull/61) by @kurtmckee
- Migrate from `zstandard` to `pyzstd`, which is now included in the Python 3.14 standard library, also part of [#61](https://github.com/colour-science/flask-compress/pull/61)
- Restore `flask_compress.DictCache()` that is needed to configure Flask-Compress with caching, without relying on [Flask-Caching](https://pypi.org/project/Flask-Caching/)
## 1.17 (2024-10-14)
- Add support for Python 3.13 in tox and classifiers
- Expand mimetype defaults, see [#57](https://github.com/colour-science/flask-compress/pull/57)
- Minor performance improvements, cache the algorithm selection logic
## 1.16 (2024-10-13)
- Drop support for Python 3.8 and lower, see [#46](https://github.com/colour-science/flask-compress/pull/46)
- Fix cache pollution caused by caching compression results for different compression algorithms, see [#53](https://github.com/colour-science/flask-compress/issues/53)
## 1.15 (2024-04-24)
- Add support of Zstandard compression.
## 1.14 (2023-09-11)
- Add `text/javascript` mimetype. See [#41](https://github.com/colour-science/flask-compress/pull/41)
- Use [brotlicffi](https://github.com/python-hyper/brotlicffi) for PyPy
## 1.13 (2022-09-21)
- Re-enable compression for streaming, but hide it behind a new option `COMPRESS_STREAMS` that defaults to `True`. See [#36](https://github.com/colour-science/flask-compress/pull/36)
## 1.12 (2022-04-28)
- Disable compression when response is streamed, see [#31](https://github.com/colour-science/flask-compress/pull/31)
## 1.11 (2022-03-01)
- When compression is enabled, *accept-encoding* is always added to the `Vary` header, fixes [#28](https://github.com/colour-science/flask-compress/issues/28)
## 1.10.0 (2021-06-15)
- Automate the release process with GitHub Actions
- Use `setuptools_scm` to manage package versions
- The layout is now an actual package rather than a single module
- Clean up unused files
## 1.9.0 (2021-02-12)
- Add support for the `identity` value in *accept-encoding*, fixes [#19](https://github.com/colour-science/flask-compress/issues/19)
## 1.8.0 (2020-11-03)
- Support ETag header as defined in *RFC7232* [#17](https://github.com/colour-science/flask-compress/pull/17)
- Implement per-view compression [#14](https://github.com/colour-science/flask-compress/pull/14)
## 1.7.0 (2020-10-09)
- The following parameters to control Brotli compression are now available: [#10](https://github.com/colour-science/flask-compress/pull/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](https://github.com/colour-science/flask-compress/pull/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`
## 1.6.0 (2020-10-05)
- Support for multiple compression algorithms and quality factors [#7](https://github.com/colour-science/flask-compress/pull/7)
- Modified default compression settings to use Brotli when available before `gzip`
## 1.5.0 (2020-05-09)
- Add Brotli support [#1](https://github.com/colour-science/flask-compress/pull/1)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465943.469606
flask_compress-1.25/Flask_Compress.egg-info/ 0000755 0001751 0001751 00000000000 15252212527 020532 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4453921
flask_compress-1.25/Flask_Compress.egg-info/PKG-INFO 0000644 0001751 0001751 00000025636 15252212527 021643 0 ustar 00runner runner Metadata-Version: 2.4
Name: Flask-Compress
Version: 1.25
Summary: Compress responses in your Flask app with gzip, deflate, brotli or zstandard.
Home-page: https://github.com/colour-science/flask-compress
Author: Thomas Mansencal
Author-email: thomas.mansencal@gmail.com
License: MIT
Platform: any
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: flask
Requires-Dist: brotli; platform_python_implementation != "PyPy"
Requires-Dist: brotlicffi; platform_python_implementation == "PyPy"
Requires-Dist: backports.zstd; python_version < "3.14"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: platform
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary
# Flask-Compress
[](https://github.com/colour-science/flask-compress/actions/workflows/ci.yaml)
[](https://pypi.python.org/pypi/Flask-Compress)
[](https://pypi.python.org/pypi/Flask-Compress)
Flask-Compress allows you to easily compress your [Flask](http://flask.pocoo.org/) application's responses with gzip, deflate, brotli or zstd. It originally started as a fork of [Flask-gzip](https://github.com/closeio/Flask-gzip). Supported Python versions are 3.9 and newer.
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 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 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
If you use pip then installation is simply:
```shell
$ pip install flask-compress
```
or, if you want the latest github version:
```shell
$ pip install git+git://github.com/colour-science/flask-compress.git
```
## Using Flask-Compress
### 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
from flask_compress import Compress
app = Flask(__name__)
Compress(app)
```
In many cases, however, one cannot expect a Flask instance to be ready at import time, and a common pattern is to return a Flask instance from within a function only after other configuration details have been taken care of. In these cases, Flask-Compress provides a simple function, `flask_compress.Compress.init_app`, which takes your application as an argument.
```python
from flask import Flask
from flask_compress import Compress
compress = Compress()
def start_app():
app = Flask(__name__)
compress.init_app(app)
return app
```
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
```
### Cache example
Flask-Compress can be integrated with caching mechanisms to serve compressed responses directly from the cache. This can significantly reduce server load and response times.
Here is an example of how to configure Flask-Compress with caching using [Flask-Caching](https://pypi.org/project/Flask-Caching/).
The example demonstrates how to create a simple cache instance with a 1-hour timeout, and use it to cache compressed responses for incoming requests.
```python
from flask import Flask
from flask_compress import Compress
from flask_cache import Cache
# Initializing flask app
app = Flask(__name__)
cache = Cache(config={
'CACHE_TYPE': 'SimpleCache',
'CACHE_DEFAULT_TIMEOUT': 60*60 # 1 hour cache timeout
})
cache.init_app(app)
# Define a function to return cache key for incoming requests
def get_cache_key(request):
return request.url
# Initialize Flask-Compress
compress = Compress()
compress.init_app(app)
# Set up cache for compressed responses
compress.cache = cache
compress.cache_key = get_cache_key
```
If you do not want to pull an external dependency, you can use a simple in-memory cache using `compress.cache = flask_compress.DictCache()`.
## ETag support
Flask-Compress supports ETag headers for conditional requests. When a client makes a request with an `If-None-Match` header, Flask-Compress will evaluate the ETag and return a `304 Not Modified` response if the resource has not changed. This helps to reduce bandwidth usage and improve performance for clients that support caching.
To disable ETag support, set the `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` configuration option to `False` in your Flask application settings.
> For streaming responses, ETag support is disabled by default, as this effectively requires buffering the entire response in memory to compute the ETag. If you want to enable ETag support for streaming responses, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, which defaults to `["static"]` for static files served by Flask.
## Streaming support
Flask-Compress supports compressing streaming responses. By default, streaming responses are compressed using the algorithms specified in the `COMPRESS_ALGORITHM_STREAMING` configuration option.
To disable streaming compression, set the `COMPRESS_STREAMS` configuration option to `False` in your Flask application settings.
> As mentioned above, ETag support is disabled by default for streaming responses. If you want to enable it for specific endpoints, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, but this will require buffering the entire response in memory to compute the ETag.
## Options
Within your Flask application's settings you can provide the following settings to control the behavior of Flask-Compress. None of the settings are required.
| Option | Description | Default |
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. | `[`
`'text/html',`
`'text/css',`
`'text/plain',`
`'text/xml',`
`'text/x-component',`
`'text/javascript',`
`'application/x-javascript',`
`'application/javascript',`
`'application/json',`
`'application/manifest+json',`
`'application/vnd.api+json',`
`'application/xml',`
`'application/xhtml+xml',`
`'application/rss+xml',`
`'application/atom+xml',`
`'application/vnd.ms-fontobject',`
`'application/x-font-ttf',`
`'application/x-font-opentype',`
`'application/x-font-truetype',`
`'image/svg+xml',`
`'image/x-icon',`
`'image/vnd.microsoft.icon',`
`'font/ttf',`
`'font/eot',`
`'font/otf',`
`'font/opentype',`
`]` |
| `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_ZSTD_LEVEL` | Specifies the ZStandard compression level. Ranges from 1 to 22. Levels >= 20, labeled ultra, should be used with caution, as they require more memory. 0 means use the default level. -131072 to -1, negative levels extend the range of speed vs ratio preferences. The lower the level, the faster the speed, but at the cost of compression ratio. | `3` |
| `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` | Supported compression algorithms. | `['zstd', 'br', 'gzip', 'deflate']` |
| `COMPRESS_ALGORITHM_STREAMING` | Supported compression algorithms for streaming. | `['zstd', 'br', 'deflate']` |
| `COMPRESS_STREAMS` | Compress streaming responses. | `True` |
| `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` | Compress evaluates conditional requests. | `True` |
| `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` | Streaming endpoints where we evaluate conditional requests. | `["static"]` |
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4649947
flask_compress-1.25/Flask_Compress.egg-info/SOURCES.txt 0000644 0001751 0001751 00000001125 15252212527 022415 0 ustar 00runner runner .gitignore
.pre-commit-config.yaml
CHANGELOG.md
LICENSE.txt
README.md
pyproject.toml
setup.py
tox.ini
.github/dependabot.yaml
.github/workflows/ci.yaml
Flask_Compress.egg-info/PKG-INFO
Flask_Compress.egg-info/SOURCES.txt
Flask_Compress.egg-info/dependency_links.txt
Flask_Compress.egg-info/requires.txt
Flask_Compress.egg-info/top_level.txt
flask_compress/__init__.py
flask_compress/_version.py
flask_compress/compat.py
flask_compress/flask_compress.py
flask_compress/py.typed
tests/test_flask_compress.py
tests/static/1.png
tests/templates/large.html
tests/templates/small.html
tests/web/test.xml ././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465943.445512
flask_compress-1.25/Flask_Compress.egg-info/dependency_links.txt 0000644 0001751 0001751 00000000001 15252212527 024600 0 ustar 00runner runner
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4458597
flask_compress-1.25/Flask_Compress.egg-info/requires.txt 0000644 0001751 0001751 00000000235 15252212527 023132 0 ustar 00runner runner flask
[:platform_python_implementation != "PyPy"]
brotli
[:platform_python_implementation == "PyPy"]
brotlicffi
[:python_version < "3.14"]
backports.zstd
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4459372
flask_compress-1.25/Flask_Compress.egg-info/top_level.txt 0000644 0001751 0001751 00000000017 15252212527 023262 0 ustar 00runner runner flask_compress
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/LICENSE.txt 0000644 0001751 0001751 00000002075 15252212521 015746 0 ustar 00runner runner The MIT License (MIT)
Copyright (c) 2013-2017 William Fagan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4707832
flask_compress-1.25/PKG-INFO 0000644 0001751 0001751 00000025636 15252212527 015236 0 ustar 00runner runner Metadata-Version: 2.4
Name: Flask-Compress
Version: 1.25
Summary: Compress responses in your Flask app with gzip, deflate, brotli or zstandard.
Home-page: https://github.com/colour-science/flask-compress
Author: Thomas Mansencal
Author-email: thomas.mansencal@gmail.com
License: MIT
Platform: any
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: flask
Requires-Dist: brotli; platform_python_implementation != "PyPy"
Requires-Dist: brotlicffi; platform_python_implementation == "PyPy"
Requires-Dist: backports.zstd; python_version < "3.14"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: platform
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary
# Flask-Compress
[](https://github.com/colour-science/flask-compress/actions/workflows/ci.yaml)
[](https://pypi.python.org/pypi/Flask-Compress)
[](https://pypi.python.org/pypi/Flask-Compress)
Flask-Compress allows you to easily compress your [Flask](http://flask.pocoo.org/) application's responses with gzip, deflate, brotli or zstd. It originally started as a fork of [Flask-gzip](https://github.com/closeio/Flask-gzip). Supported Python versions are 3.9 and newer.
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 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 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
If you use pip then installation is simply:
```shell
$ pip install flask-compress
```
or, if you want the latest github version:
```shell
$ pip install git+git://github.com/colour-science/flask-compress.git
```
## Using Flask-Compress
### 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
from flask_compress import Compress
app = Flask(__name__)
Compress(app)
```
In many cases, however, one cannot expect a Flask instance to be ready at import time, and a common pattern is to return a Flask instance from within a function only after other configuration details have been taken care of. In these cases, Flask-Compress provides a simple function, `flask_compress.Compress.init_app`, which takes your application as an argument.
```python
from flask import Flask
from flask_compress import Compress
compress = Compress()
def start_app():
app = Flask(__name__)
compress.init_app(app)
return app
```
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
```
### Cache example
Flask-Compress can be integrated with caching mechanisms to serve compressed responses directly from the cache. This can significantly reduce server load and response times.
Here is an example of how to configure Flask-Compress with caching using [Flask-Caching](https://pypi.org/project/Flask-Caching/).
The example demonstrates how to create a simple cache instance with a 1-hour timeout, and use it to cache compressed responses for incoming requests.
```python
from flask import Flask
from flask_compress import Compress
from flask_cache import Cache
# Initializing flask app
app = Flask(__name__)
cache = Cache(config={
'CACHE_TYPE': 'SimpleCache',
'CACHE_DEFAULT_TIMEOUT': 60*60 # 1 hour cache timeout
})
cache.init_app(app)
# Define a function to return cache key for incoming requests
def get_cache_key(request):
return request.url
# Initialize Flask-Compress
compress = Compress()
compress.init_app(app)
# Set up cache for compressed responses
compress.cache = cache
compress.cache_key = get_cache_key
```
If you do not want to pull an external dependency, you can use a simple in-memory cache using `compress.cache = flask_compress.DictCache()`.
## ETag support
Flask-Compress supports ETag headers for conditional requests. When a client makes a request with an `If-None-Match` header, Flask-Compress will evaluate the ETag and return a `304 Not Modified` response if the resource has not changed. This helps to reduce bandwidth usage and improve performance for clients that support caching.
To disable ETag support, set the `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` configuration option to `False` in your Flask application settings.
> For streaming responses, ETag support is disabled by default, as this effectively requires buffering the entire response in memory to compute the ETag. If you want to enable ETag support for streaming responses, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, which defaults to `["static"]` for static files served by Flask.
## Streaming support
Flask-Compress supports compressing streaming responses. By default, streaming responses are compressed using the algorithms specified in the `COMPRESS_ALGORITHM_STREAMING` configuration option.
To disable streaming compression, set the `COMPRESS_STREAMS` configuration option to `False` in your Flask application settings.
> As mentioned above, ETag support is disabled by default for streaming responses. If you want to enable it for specific endpoints, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, but this will require buffering the entire response in memory to compute the ETag.
## Options
Within your Flask application's settings you can provide the following settings to control the behavior of Flask-Compress. None of the settings are required.
| Option | Description | Default |
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. | `[`
`'text/html',`
`'text/css',`
`'text/plain',`
`'text/xml',`
`'text/x-component',`
`'text/javascript',`
`'application/x-javascript',`
`'application/javascript',`
`'application/json',`
`'application/manifest+json',`
`'application/vnd.api+json',`
`'application/xml',`
`'application/xhtml+xml',`
`'application/rss+xml',`
`'application/atom+xml',`
`'application/vnd.ms-fontobject',`
`'application/x-font-ttf',`
`'application/x-font-opentype',`
`'application/x-font-truetype',`
`'image/svg+xml',`
`'image/x-icon',`
`'image/vnd.microsoft.icon',`
`'font/ttf',`
`'font/eot',`
`'font/otf',`
`'font/opentype',`
`]` |
| `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_ZSTD_LEVEL` | Specifies the ZStandard compression level. Ranges from 1 to 22. Levels >= 20, labeled ultra, should be used with caution, as they require more memory. 0 means use the default level. -131072 to -1, negative levels extend the range of speed vs ratio preferences. The lower the level, the faster the speed, but at the cost of compression ratio. | `3` |
| `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` | Supported compression algorithms. | `['zstd', 'br', 'gzip', 'deflate']` |
| `COMPRESS_ALGORITHM_STREAMING` | Supported compression algorithms for streaming. | `['zstd', 'br', 'deflate']` |
| `COMPRESS_STREAMS` | Compress streaming responses. | `True` |
| `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` | Compress evaluates conditional requests. | `True` |
| `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` | Streaming endpoints where we evaluate conditional requests. | `["static"]` |
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/README.md 0000644 0001751 0001751 00000022435 15252212521 015404 0 ustar 00runner runner # Flask-Compress
[](https://github.com/colour-science/flask-compress/actions/workflows/ci.yaml)
[](https://pypi.python.org/pypi/Flask-Compress)
[](https://pypi.python.org/pypi/Flask-Compress)
Flask-Compress allows you to easily compress your [Flask](http://flask.pocoo.org/) application's responses with gzip, deflate, brotli or zstd. It originally started as a fork of [Flask-gzip](https://github.com/closeio/Flask-gzip). Supported Python versions are 3.9 and newer.
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 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 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
If you use pip then installation is simply:
```shell
$ pip install flask-compress
```
or, if you want the latest github version:
```shell
$ pip install git+git://github.com/colour-science/flask-compress.git
```
## Using Flask-Compress
### 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
from flask_compress import Compress
app = Flask(__name__)
Compress(app)
```
In many cases, however, one cannot expect a Flask instance to be ready at import time, and a common pattern is to return a Flask instance from within a function only after other configuration details have been taken care of. In these cases, Flask-Compress provides a simple function, `flask_compress.Compress.init_app`, which takes your application as an argument.
```python
from flask import Flask
from flask_compress import Compress
compress = Compress()
def start_app():
app = Flask(__name__)
compress.init_app(app)
return app
```
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
```
### Cache example
Flask-Compress can be integrated with caching mechanisms to serve compressed responses directly from the cache. This can significantly reduce server load and response times.
Here is an example of how to configure Flask-Compress with caching using [Flask-Caching](https://pypi.org/project/Flask-Caching/).
The example demonstrates how to create a simple cache instance with a 1-hour timeout, and use it to cache compressed responses for incoming requests.
```python
from flask import Flask
from flask_compress import Compress
from flask_cache import Cache
# Initializing flask app
app = Flask(__name__)
cache = Cache(config={
'CACHE_TYPE': 'SimpleCache',
'CACHE_DEFAULT_TIMEOUT': 60*60 # 1 hour cache timeout
})
cache.init_app(app)
# Define a function to return cache key for incoming requests
def get_cache_key(request):
return request.url
# Initialize Flask-Compress
compress = Compress()
compress.init_app(app)
# Set up cache for compressed responses
compress.cache = cache
compress.cache_key = get_cache_key
```
If you do not want to pull an external dependency, you can use a simple in-memory cache using `compress.cache = flask_compress.DictCache()`.
## ETag support
Flask-Compress supports ETag headers for conditional requests. When a client makes a request with an `If-None-Match` header, Flask-Compress will evaluate the ETag and return a `304 Not Modified` response if the resource has not changed. This helps to reduce bandwidth usage and improve performance for clients that support caching.
To disable ETag support, set the `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` configuration option to `False` in your Flask application settings.
> For streaming responses, ETag support is disabled by default, as this effectively requires buffering the entire response in memory to compute the ETag. If you want to enable ETag support for streaming responses, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, which defaults to `["static"]` for static files served by Flask.
## Streaming support
Flask-Compress supports compressing streaming responses. By default, streaming responses are compressed using the algorithms specified in the `COMPRESS_ALGORITHM_STREAMING` configuration option.
To disable streaming compression, set the `COMPRESS_STREAMS` configuration option to `False` in your Flask application settings.
> As mentioned above, ETag support is disabled by default for streaming responses. If you want to enable it for specific endpoints, you can add the endpoint name to the `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` configuration option, but this will require buffering the entire response in memory to compute the ETag.
## Options
Within your Flask application's settings you can provide the following settings to control the behavior of Flask-Compress. None of the settings are required.
| Option | Description | Default |
| ------ | ----------- | ------- |
| `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. | `[`
`'text/html',`
`'text/css',`
`'text/plain',`
`'text/xml',`
`'text/x-component',`
`'text/javascript',`
`'application/x-javascript',`
`'application/javascript',`
`'application/json',`
`'application/manifest+json',`
`'application/vnd.api+json',`
`'application/xml',`
`'application/xhtml+xml',`
`'application/rss+xml',`
`'application/atom+xml',`
`'application/vnd.ms-fontobject',`
`'application/x-font-ttf',`
`'application/x-font-opentype',`
`'application/x-font-truetype',`
`'image/svg+xml',`
`'image/x-icon',`
`'image/vnd.microsoft.icon',`
`'font/ttf',`
`'font/eot',`
`'font/otf',`
`'font/opentype',`
`]` |
| `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_ZSTD_LEVEL` | Specifies the ZStandard compression level. Ranges from 1 to 22. Levels >= 20, labeled ultra, should be used with caution, as they require more memory. 0 means use the default level. -131072 to -1, negative levels extend the range of speed vs ratio preferences. The lower the level, the faster the speed, but at the cost of compression ratio. | `3` |
| `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` | Supported compression algorithms. | `['zstd', 'br', 'gzip', 'deflate']` |
| `COMPRESS_ALGORITHM_STREAMING` | Supported compression algorithms for streaming. | `['zstd', 'br', 'deflate']` |
| `COMPRESS_STREAMS` | Compress streaming responses. | `True` |
| `COMPRESS_EVALUATE_CONDITIONAL_REQUEST` | Compress evaluates conditional requests. | `True` |
| `COMPRESS_STREAMING_ENDPOINT_CONDITIONAL` | Streaming endpoints where we evaluate conditional requests. | `["static"]` |
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4700127
flask_compress-1.25/flask_compress/ 0000755 0001751 0001751 00000000000 15252212527 017140 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841197
flask_compress-1.25/flask_compress/__init__.py 0000644 0001751 0001751 00000000661 15252212521 021246 0 ustar 00runner runner from .flask_compress import CacheBackend, Compress, DictCache
# _version.py is generated by setuptools_scm when building the package.
# It is not version-controlled, so if it is missing, this likely means that
# the imported code was installed from the git repo without using "editable" mode.
try:
from ._version import __version__
except ImportError:
__version__ = "0"
__all__ = ("CacheBackend", "Compress", "DictCache")
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4127991
flask_compress-1.25/flask_compress/_version.py 0000644 0001751 0001751 00000000025 15252212527 021333 0 ustar 00runner runner __version__ = "1.25"
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841503
flask_compress-1.25/flask_compress/compat.py 0000644 0001751 0001751 00000001335 15252212521 020771 0 ustar 00runner runner # compression compatibility
# -------------------------
#
# Use the Python 3.14 `compression` module if possible.
# If unavailable, mimic the module structure for backwards compatibility.
#
# When Python 3.14 is the lowest supported version,
# the `import compression.*` statements can move to `flask_compress.py`
# and this try/except block can be removed from this file.
import sys
if sys.version_info >= (3, 14):
import compression.gzip
import compression.zlib
import compression.zstd
else: # Python <= 3.13
import gzip
import types
import zlib
from backports import zstd
compression = types.SimpleNamespace()
compression.gzip = gzip
compression.zlib = zlib
compression.zstd = zstd
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841503
flask_compress-1.25/flask_compress/flask_compress.py 0000644 0001751 0001751 00000035206 15252212521 022525 0 ustar 00runner runner # Authors: William Fagan
# Copyright (c) 2013-2017 William Fagan
# License: The MIT License (MIT)
from __future__ import annotations
import functools
from collections import defaultdict
from collections.abc import Callable, Iterator
from functools import lru_cache
from typing import Any, Protocol
try:
import brotlicffi as brotli
except ImportError:
import brotli
from flask import (
Flask,
after_this_request,
current_app,
make_response,
request,
stream_with_context,
)
from flask.wrappers import Response
from .compat import compression
class CacheBackend(Protocol):
def get(self, key: str) -> bytes | None: ...
def set(self, key: str, value: bytes) -> bool | None: ...
class DictCache:
def __init__(self) -> None:
self.data: dict[str, bytes] = {}
def get(self, key: str) -> bytes | None:
return self.data.get(key)
def set(self, key: str, value: bytes) -> None:
self.data[key] = value
@lru_cache(maxsize=128)
def _choose_algorithm(algorithms: tuple[str, ...], accept_encoding: str) -> str | None:
"""
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 algorithms: Tuple of supported compression algorithms
:param accept_encoding: Content of the `Accept-Encoding` header
:return: name of a compression algorithm (`gzip`, `deflate`, `br`, 'zstd')
or `None` if the client and server don't agree on any.
"""
# A flag denoting that client requested using any (`*`) algorithm,
# in case a specific one is not supported by the server
fallback_to_any = False
# Map quality factors to requested algorithm names.
algos_by_quality: defaultdict[float, set[str | None]] = defaultdict(set)
# Set of supported algorithms
server_algos_set = set(algorithms)
for part in accept_encoding.lower().split(","):
part = part.strip()
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:
quality = 1.0
else:
# Otherwise, use the default quality
algo = part
quality = 1.0
if algo == "*":
if quality > 0:
fallback_to_any = True
elif algo == "identity": # identity means 'no compression asked'
algos_by_quality[quality].add(None)
elif algo in server_algos_set:
algos_by_quality[quality].add(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.
for _, viable_algos in sorted(algos_by_quality.items(), reverse=True):
if len(viable_algos) == 1:
return viable_algos.pop()
elif len(viable_algos) > 1:
for server_algo in algorithms:
if server_algo in viable_algos:
return server_algo
if fallback_to_any:
return algorithms[0]
return None
def _format(algo: str | list[str]) -> tuple[str, ...]:
"""Format the algorithm configuration into a tuple of strings.
>>> _format("gzip, deflate, br")
('gzip', 'deflate', 'br')
>>> _format(["gzip", "deflate", "br"])
('gzip', 'deflate', 'br')
"""
if isinstance(algo, str):
return tuple(i.strip() for i in algo.split(","))
else:
return tuple(algo)
class Compress:
"""
The Compress object allows your application to use Flask-Compress.
When initialising a Compress object you may optionally provide your
:class:`flask.Flask` application object if it is ready. Otherwise,
you may provide it later by using the :meth:`init_app` method.
:param app: optional :class:`flask.Flask` application object
:type app: :class:`flask.Flask` or None
"""
cache: CacheBackend | None
cache_key: Callable[..., str] | None
compress_mimetypes_set: set[str]
enabled_algorithms: tuple[str, ...]
streaming_algorithms: tuple[str, ...]
streaming_endpoint_with_conditional: set[str]
def __init__(self, app: Flask | None = None) -> None:
"""
An alternative way to pass your :class:`flask.Flask` application
object to Flask-Compress. :meth:`init_app` also takes care of some
default `settings`_.
:param app: the :class:`flask.Flask` application object.
"""
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app: Flask) -> None:
defaults = [
(
"COMPRESS_MIMETYPES",
[
"text/html",
"text/css",
"text/plain",
"text/xml",
"text/x-component",
"text/javascript", # Obsolete (RFC 9239)
"application/x-javascript",
"application/javascript",
"application/json",
"application/manifest+json",
"application/vnd.api+json",
"application/xml",
"application/xhtml+xml",
"application/rss+xml",
"application/atom+xml",
"application/vnd.ms-fontobject",
"application/x-font-ttf",
"application/x-font-opentype",
"application/x-font-truetype",
"image/svg+xml",
"image/x-icon",
"image/vnd.microsoft.icon",
"font/ttf",
"font/eot",
"font/otf",
"font/opentype",
],
),
("COMPRESS_LEVEL", 6),
("COMPRESS_BR_LEVEL", 4),
("COMPRESS_BR_MODE", 0),
("COMPRESS_BR_WINDOW", 22),
("COMPRESS_BR_BLOCK", 0),
("COMPRESS_ZSTD_LEVEL", 3),
("COMPRESS_DEFLATE_LEVEL", -1),
("COMPRESS_MIN_SIZE", 500),
("COMPRESS_CACHE_KEY", None),
("COMPRESS_CACHE_BACKEND", None),
("COMPRESS_REGISTER", True),
("COMPRESS_STREAMS", True),
("COMPRESS_EVALUATE_CONDITIONAL_REQUEST", True),
("COMPRESS_STREAMING_ENDPOINT_CONDITIONAL", ["static"]),
("COMPRESS_ALGORITHM", ["zstd", "br", "gzip", "deflate"]),
("COMPRESS_ALGORITHM_STREAMING", ["zstd", "br", "deflate"]), # no gzip
]
for k, v in defaults:
app.config.setdefault(k, v)
backend = app.config["COMPRESS_CACHE_BACKEND"]
self.cache = backend() if backend else None
self.cache_key = app.config["COMPRESS_CACHE_KEY"]
self.compress_mimetypes_set = set(app.config["COMPRESS_MIMETYPES"])
self.enabled_algorithms = _format(app.config["COMPRESS_ALGORITHM"])
self.streaming_algorithms = _format(app.config["COMPRESS_ALGORITHM_STREAMING"])
self.streaming_endpoint_with_conditional = set(
app.config["COMPRESS_STREAMING_ENDPOINT_CONDITIONAL"]
)
if app.config["COMPRESS_REGISTER"] and app.config["COMPRESS_MIMETYPES"]:
app.after_request(self.after_request)
def after_request(self, response: Response | None) -> Response:
if not response:
return make_response("", 204)
app = self.app or current_app
vary = response.headers.get("Vary")
if not vary:
response.headers["Vary"] = "Accept-Encoding"
elif "accept-encoding" not in vary.lower():
response.headers["Vary"] = f"{vary}, Accept-Encoding"
accept_encoding = request.headers.get("Accept-Encoding", "")
streaming_compressed = response.is_streamed and app.config["COMPRESS_STREAMS"]
streaming_conditional = response.is_streamed and (
request.endpoint in self.streaming_endpoint_with_conditional
)
algorithms = (
self.streaming_algorithms
if streaming_compressed
else self.enabled_algorithms
)
chosen_algorithm = _choose_algorithm(algorithms, accept_encoding)
if (
chosen_algorithm is None
or response.mimetype not in self.compress_mimetypes_set
or response.status_code < 200
or response.status_code >= 300
or (response.is_streamed and not app.config["COMPRESS_STREAMS"])
or "Content-Encoding" in response.headers
or (
response.content_length is not None
and response.content_length < app.config["COMPRESS_MIN_SIZE"]
)
):
return response
response.direct_passthrough = False
response.headers["Content-Encoding"] = chosen_algorithm
if streaming_compressed:
chunks = response.iter_encoded()
_gen_compressed_content = _compress_chunks(app, chunks, chosen_algorithm)
response.response = stream_with_context(_gen_compressed_content)
response.headers.pop("Content-Length", None)
else:
if self.cache is not None:
assert self.cache_key is not None
key = f"{chosen_algorithm};{self.cache_key(request)}"
compressed_content = self.cache.get(key)
if compressed_content is None:
data = response.get_data()
compressed_content = _compress_data(app, data, chosen_algorithm)
self.cache.set(key, compressed_content)
else:
data = response.get_data()
compressed_content = _compress_data(app, data, chosen_algorithm)
response.set_data(compressed_content)
response.headers["Content-Length"] = response.content_length
# "123456789" => "123456789:gzip" - A strong ETag validator
# W/"123456789" => W/"123456789:gzip" - A weak ETag validator
etag, is_weak = response.get_etag()
if etag and not is_weak:
response.set_etag(f"{etag}:{chosen_algorithm}", weak=False)
if (
app.config["COMPRESS_EVALUATE_CONDITIONAL_REQUEST"]
and request.method in ("GET", "HEAD")
and (not response.is_streamed or streaming_conditional)
):
response.make_conditional(request)
return response
def compressed(self) -> Callable[..., Callable[..., Any]]:
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(f)
def decorated_function(*args: Any, **kwargs: Any) -> Any:
@after_this_request
def compressor(response: Response) -> Response:
return self.after_request(response)
return f(*args, **kwargs)
return decorated_function
return decorator
def _compress_data(app: Flask, data: bytes, algorithm: str) -> bytes:
if algorithm == "zstd":
return compression.zstd.compress( # type: ignore[no-any-return]
data, app.config["COMPRESS_ZSTD_LEVEL"]
)
if algorithm == "gzip":
return compression.gzip.compress( # type: ignore[no-any-return]
data, app.config["COMPRESS_LEVEL"]
)
if algorithm == "deflate":
return compression.zlib.compress( # type: ignore[no-any-return]
data, app.config["COMPRESS_DEFLATE_LEVEL"]
)
if algorithm == "br":
return brotli.compress( # type: ignore[no-any-return]
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"],
)
raise ValueError(f"Unknown compression algorithm: {algorithm}")
def _uncompress_data(data: bytes, algorithm: str) -> bytes:
# This is used for tests purposes only.
if algorithm == "zstd":
return compression.zstd.decompress(data) # type: ignore[no-any-return]
if algorithm == "gzip":
return compression.gzip.decompress(data) # type: ignore[no-any-return]
if algorithm == "deflate":
return compression.zlib.decompress(data) # type: ignore[no-any-return]
if algorithm == "br":
return brotli.decompress(data) # type: ignore[no-any-return]
raise ValueError(f"Unknown compression algorithm: {algorithm}")
def _compress_chunks(
app: Flask, chunks: Iterator[bytes], algorithm: str
) -> Iterator[bytes]:
if algorithm == "zstd":
level = app.config["COMPRESS_ZSTD_LEVEL"]
compressor = compression.zstd.ZstdCompressor(level=level)
for data in chunks:
out = compressor.compress(data)
if out:
yield out
out = compressor.flush()
if out:
yield out
elif algorithm == "gzip":
level = app.config["COMPRESS_LEVEL"]
compressor = compression.zlib.compressobj(
level,
compression.zlib.DEFLATED,
compression.zlib.MAX_WBITS + 16,
)
for data in chunks:
out = compressor.compress(data)
if out:
yield out
out = compressor.flush()
if out:
yield out
elif algorithm == "deflate":
level = app.config["COMPRESS_DEFLATE_LEVEL"]
compressor = compression.zlib.compressobj(level=level)
for data in chunks:
out = compressor.compress(data)
if out:
yield out
out = compressor.flush()
if out:
yield out
elif algorithm == "br":
compressor = brotli.Compressor(
mode=app.config["COMPRESS_BR_MODE"],
quality=app.config["COMPRESS_BR_LEVEL"],
lgwin=app.config["COMPRESS_BR_WINDOW"],
lgblock=app.config["COMPRESS_BR_BLOCK"],
)
for data in chunks:
out = compressor.process(data)
if out:
yield out
out = compressor.finish()
if out:
yield out
else:
raise ValueError(f"Unsupported streaming algorithm: {algorithm}")
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841503
flask_compress-1.25/flask_compress/py.typed 0000644 0001751 0001751 00000000000 15252212521 020617 0 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841503
flask_compress-1.25/pyproject.toml 0000644 0001751 0001751 00000001403 15252212521 017031 0 ustar 00runner runner [build-system]
requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]<8"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
write_to = "flask_compress/_version.py"
write_to_template = "__version__ = \"{version}\"\n"
# coverage
# --------
[tool.coverage.run]
relative_files = true
parallel = true
branch = true
source = [
"flask_compress",
"tests",
]
[tool.coverage.paths]
source = [
"flask_compress",
"*/site-packages",
]
[tool.coverage.report]
skip_covered = true
fail_under = 94
[tool.coverage.html]
directory = "htmlcov/"
skip_covered = false
# type checking
# --------------
[tool.mypy]
strict = true
[[tool.mypy.overrides]]
module = ["brotli", "brotlicffi", "backports.*", "flask_caching"]
ignore_missing_imports = true
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4710112
flask_compress-1.25/setup.cfg 0000644 0001751 0001751 00000000046 15252212527 015746 0 ustar 00runner runner [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1789465936.841503
flask_compress-1.25/setup.py 0000644 0001751 0001751 00000003416 15252212521 015635 0 ustar 00runner runner from setuptools import find_packages, setup
with open("README.md") as fl:
LONG_DESCRIPTION = fl.read()
setup(
name="Flask-Compress",
use_scm_version=True,
url="https://github.com/colour-science/flask-compress",
license="MIT",
author="Thomas Mansencal",
author_email="thomas.mansencal@gmail.com",
description=(
"Compress responses in your Flask app with gzip, deflate, brotli or zstandard."
),
long_description=LONG_DESCRIPTION,
long_description_content_type="text/markdown",
packages=find_packages(exclude=["tests"]),
package_data={"flask_compress": ["py.typed"]},
include_package_data=True,
platforms="any",
python_requires=">=3.9",
install_requires=[
"flask",
"brotli; platform_python_implementation!='PyPy'",
"brotlicffi; platform_python_implementation=='PyPy'",
"backports.zstd; python_version<'3.14'",
],
setup_requires=[
"setuptools_scm",
],
classifiers=[
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
],
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4701085
flask_compress-1.25/tests/ 0000755 0001751 0001751 00000000000 15252212527 015267 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465943.4701715
flask_compress-1.25/tests/static/ 0000755 0001751 0001751 00000000000 15252212527 016556 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1789465936.8417542
flask_compress-1.25/tests/static/1.png 0000644 0001751 0001751 00000000357 15252212521 017423 0 ustar 00runner runner ‰PNG
IHDR 7nù$ gAMA ±�üa cHRM z&