From 25d5b2fce785317e4e7fac56b027abbccd84b1da Mon Sep 17 00:00:00 2001 From: Reimar Bauer Date: Wed, 7 Oct 2026 10:40:12 +0200 Subject: [PATCH] Update to Flask 3.1 and Werkzeug 3.1 and keep the size limits of MSColab requests --- docs/mscolab.rst | 5 +- mslib/mscolab/app/__init__.py | 40 ++-- pixi.lock | 322 ++++++++++++++--------------- pixi.toml | 9 +- tests/_test_mscolab/test_server.py | 57 ++++- 5 files changed, 243 insertions(+), 190 deletions(-) diff --git a/docs/mscolab.rst b/docs/mscolab.rst index 1dcc27030..10198b4b9 100644 --- a/docs/mscolab.rst +++ b/docs/mscolab.rst @@ -306,7 +306,10 @@ You can use the `Preview` button to see how your text is formatted before sendin There is also support for image/document upload. You can set the upload size limit with ``MAX_UPLOAD_SIZE`` in the mscolab_settings.py file. The default limit is 2 MiB. Any other request, e.g. creating an operation from a large -flight track, is limited by ``MAX_CONTENT_LENGTH``, by default 16 MiB. +flight track, is limited by ``MAX_CONTENT_LENGTH``, by default 16 MiB. A single form field is limited by Flask's +``MAX_FORM_MEMORY_SIZE``, by default 500 kB; only the flight track of a new operation may be as large as the whole +request. Without a ``MAX_CONTENT_LENGTH`` (``None``) requests are not limited, but form fields, also the flight +track, are limited by ``MAX_FORM_MEMORY_SIZE``. Only files with an extension of ``MSCOLAB_ATTACHMENT_EXTENSIONS`` can be sent as chat attachments. By default these are flight tracks (ftml, csv, txt, kml, gpx), science data (nc, nc4, h5, json), documents (pdf, md, docx, xlsx, pptx, odt, diff --git a/mslib/mscolab/app/__init__.py b/mslib/mscolab/app/__init__.py index d56aca792..c0f380729 100644 --- a/mslib/mscolab/app/__init__.py +++ b/mslib/mscolab/app/__init__.py @@ -35,7 +35,7 @@ from flask_mail import Mail from flask_migrate import Migrate -from flask import Flask, Request, current_app, jsonify, request, url_for +from flask import Flask, current_app, jsonify, request, url_for import mslib @@ -248,25 +248,39 @@ def repair_schema(): add_token_nonce(Operations(MigrationContext.configure(conn)), conn) -class MSColabRequest(Request): +# endpoints of uploads, a request to them is limited to MAX_UPLOAD_SIZE instead of MAX_CONTENT_LENGTH +UPLOAD_ENDPOINTS = ("chat.message_attachment", "user.upload_profile_image") +# endpoints with a large form field, the flight track of a new operation; it may be as large as the whole request +LARGE_FORM_FIELD_ENDPOINTS = ("operation.create_operation",) + + +def limit_request_size(): """ Limits the size of an upload to MAX_UPLOAD_SIZE and of any other request to MAX_CONTENT_LENGTH - Flask refuses a larger request with 413 before reading it. + A form field is limited to Flask's MAX_FORM_MEMORY_SIZE (by default 500 kB), also on the endpoints anyone can + reach, e.g. /token. Only on LARGE_FORM_FIELD_ENDPOINTS it may be as large as the whole request. Flask refuses a + larger request with 413 before reading it. """ - UPLOAD_ENDPOINTS = ("chat.message_attachment", "user.upload_profile_image") - - @property - def max_content_length(self): - if current_app and self.endpoint in self.UPLOAD_ENDPOINTS: - return current_app.config["MAX_UPLOAD_SIZE"] - return super().max_content_length + if request.endpoint in UPLOAD_ENDPOINTS: + request.max_content_length = current_app.config["MAX_UPLOAD_SIZE"] + # without a MAX_CONTENT_LENGTH (None), Flask keeps MAX_FORM_MEMORY_SIZE + if request.endpoint in LARGE_FORM_FIELD_ENDPOINTS and request.max_content_length is not None: + request.max_form_memory_size = request.max_content_length # 413: Payload Too Large def error413(error): - limit = request.max_content_length / 1024 / 1024 - return jsonify({"success": False, "message": f"Request too large. The limit is {limit:.1f} MiB."}), 413 + """ + Answers a request that is too large: the whole request (MAX_CONTENT_LENGTH or MAX_UPLOAD_SIZE), a form field + (MAX_FORM_MEMORY_SIZE) or the number of form parts (MAX_FORM_PARTS); Werkzeug doesn't say which + """ + limit = request.max_content_length + if limit is not None and (request.content_length is None or request.content_length > limit): + message = f"Request too large. The limit is {limit / 1024 / 1024:.1f} MiB." + else: + message = "A form field of the request is too large, or the request has too many form parts." + return jsonify({"success": False, "message": message}), 413 class InsecureSecretError(RuntimeError): @@ -361,7 +375,7 @@ def create_app(config_object=mscolab_settings): check_basic_auth_setting(config_object, "mscolab_settings") app.register_error_handler(PasswordCheckBusy, lambda error: (str(error), 503, {"Retry-After": "10"})) # uploads are limited to MAX_UPLOAD_SIZE, other requests to MAX_CONTENT_LENGTH - app.request_class = MSColabRequest + app.before_request(limit_request_size) app.register_error_handler(413, error413) # Expose docs path for callers/tests and make it part of Flask config for consistency. app.config['DOCS_SERVER_PATH'] = DOCS_SERVER_PATH diff --git a/pixi.lock b/pixi.lock index acd330829..20bca70ba 100644 --- a/pixi.lock +++ b/pixi.lock @@ -243,10 +243,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -330,7 +329,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -339,7 +338,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - pypi: . + - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.16.1-he30d5cf_0.conda @@ -547,10 +546,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -635,7 +633,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda @@ -644,7 +642,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - - pypi: . + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -667,10 +665,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -753,7 +750,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -905,7 +902,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: . + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -928,10 +925,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -1014,7 +1010,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.8-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda @@ -1166,7 +1162,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . + - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -1190,10 +1186,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -1276,7 +1271,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda @@ -1413,7 +1408,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . + - pypi: ./ default: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -1630,10 +1625,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -1714,7 +1708,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -1723,7 +1717,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - pypi: . + - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.16.1-he30d5cf_0.conda @@ -1931,10 +1925,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -2016,7 +2009,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda @@ -2025,7 +2018,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - - pypi: . + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -2048,10 +2041,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -2131,7 +2123,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -2283,7 +2275,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: . + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -2306,10 +2298,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -2389,7 +2380,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.8-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda @@ -2541,7 +2532,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . + - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -2565,10 +2556,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -2648,7 +2638,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda @@ -2785,7 +2775,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . + - pypi: ./ dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -3041,10 +3031,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.30.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-builtins-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -3179,7 +3168,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.51.0-h28ae30b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -3188,7 +3177,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - pypi: . + - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.16.1-he30d5cf_0.conda @@ -3435,10 +3424,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-builtins-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -3573,7 +3561,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.52.4-h9be8d4e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.7.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda @@ -3582,7 +3570,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - - pypi: . + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda @@ -3628,10 +3616,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.30.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-builtins-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -3765,7 +3752,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.51.0-h28ae30b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -3933,7 +3920,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: . + - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda @@ -3979,10 +3966,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.30.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-7.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flake8-builtins-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -4116,7 +4102,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.51.0-he483a34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda @@ -4265,7 +4251,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . + - pypi: ./ docs: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -4486,10 +4472,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -4585,7 +4570,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -4594,7 +4579,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - pypi: . + - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.16.1-he30d5cf_0.conda @@ -4806,10 +4791,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -4905,7 +4889,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda @@ -4914,7 +4898,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - - pypi: . + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda @@ -4941,10 +4925,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -5039,7 +5022,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -5191,7 +5174,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: . + - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda @@ -5218,10 +5201,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -5316,7 +5298,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda @@ -5453,7 +5435,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . + - pypi: ./ tutorials: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -5738,10 +5720,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -5831,7 +5812,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.47-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -5840,7 +5821,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-bootstrap-5.3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xstatic-jquery-3.5.1.1-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - pypi: . + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -5863,10 +5844,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -5955,7 +5935,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.2.0-pyhcf101f3_0.conda @@ -6160,7 +6140,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - - pypi: . + - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.4-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda @@ -6184,10 +6164,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fastkml-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-httpauth-4.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-migrate-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flask-socketio-5.6.1-pyhcf101f3_0.conda @@ -6276,7 +6255,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unicodecsv-0.14.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wsproto-1.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wtforms-3.2.1-pyhd8ed1ab_1.conda @@ -6465,7 +6444,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . + - pypi: ./ packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda build_number: 20 @@ -6740,7 +6719,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/contourpy?source=compressed-mapping + - pkg:pypi/contourpy?source=hash-mapping size: 320553 timestamp: 1769155975008 - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.4-py311h3778330_0.conda @@ -8939,7 +8918,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 26756 timestamp: 1772445078834 - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.9.1-py311h38be061_1.conda @@ -9008,7 +8987,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/multidict?source=compressed-mapping + - pkg:pypi/multidict?source=hash-mapping size: 100649 timestamp: 1771610839808 - conda: https://conda.anaconda.org/conda-forge/linux-64/nco-5.3.6-hfb7d223_0.conda @@ -9151,7 +9130,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping + - pkg:pypi/numpy?source=hash-mapping size: 9385101 timestamp: 1770098496391 - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda @@ -9639,7 +9618,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping + - pkg:pypi/pyyaml?source=hash-mapping size: 206190 timestamp: 1770223702917 - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda @@ -9822,7 +9801,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping size: 17099824 timestamp: 1771880736635 - conda: https://conda.anaconda.org/conda-forge/linux-64/sdl2-2.32.56-h54a6638_0.conda @@ -10072,7 +10051,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/tornado?source=compressed-mapping + - pkg:pypi/tornado?source=hash-mapping size: 871254 timestamp: 1765458944370 - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py311h49ec1c0_0.conda @@ -10086,7 +10065,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/unicodedata2?source=compressed-mapping + - pkg:pypi/unicodedata2?source=hash-mapping size: 409682 timestamp: 1770909108616 - conda: https://conda.anaconda.org/conda-forge/linux-64/uvloop-0.22.1-py311h49ec1c0_1.conda @@ -10147,7 +10126,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/websockets?source=compressed-mapping + - pkg:pypi/websockets?source=hash-mapping run_exports: {} size: 361644 timestamp: 1783694703274 @@ -10809,7 +10788,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/cffi?source=compressed-mapping + - pkg:pypi/cffi?source=hash-mapping run_exports: {} size: 332764 timestamp: 1786775065183 @@ -11125,7 +11104,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/greenlet?source=compressed-mapping + - pkg:pypi/greenlet?source=hash-mapping run_exports: {} size: 284148 timestamp: 1786384077975 @@ -13939,7 +13918,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/alembic?source=compressed-mapping + - pkg:pypi/alembic?source=hash-mapping run_exports: {} size: 186094 timestamp: 1786265219360 @@ -14023,7 +14002,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/argcomplete?source=compressed-mapping + - pkg:pypi/argcomplete?source=hash-mapping run_exports: {} size: 46445 timestamp: 1782888485558 @@ -14067,7 +14046,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/arrow?source=compressed-mapping + - pkg:pypi/arrow?source=hash-mapping run_exports: {} size: 113854 timestamp: 1760831179410 @@ -14094,7 +14073,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/babel?source=compressed-mapping + - pkg:pypi/babel?source=hash-mapping size: 7684373 timestamp: 1770326844118 - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda @@ -14210,7 +14189,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/cachetools?source=compressed-mapping + - pkg:pypi/cachetools?source=hash-mapping run_exports: {} size: 21271 timestamp: 1779411598647 @@ -14233,7 +14212,7 @@ packages: - python >=3.10 license: ISC purls: - - pkg:pypi/certifi?source=compressed-mapping + - pkg:pypi/certifi?source=hash-mapping size: 151445 timestamp: 1772001170301 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda @@ -14624,8 +14603,7 @@ packages: - python-multipart - uvicorn-standard license: MIT - purls: - - pkg:pypi/fastapi?source=compressed-mapping + purls: [] run_exports: {} size: 4845 timestamp: 1784297339154 @@ -14689,7 +14667,7 @@ packages: - uvicorn-standard >=0.12.0 license: MIT purls: - - pkg:pypi/fastapi?source=compressed-mapping + - pkg:pypi/fastapi?source=hash-mapping run_exports: {} size: 104185 timestamp: 1784297339154 @@ -14742,7 +14720,7 @@ packages: - python >=3.10 license: Unlicense purls: - - pkg:pypi/filelock?source=compressed-mapping + - pkg:pypi/filelock?source=hash-mapping run_exports: {} size: 74973 timestamp: 1784344102927 @@ -14753,7 +14731,7 @@ packages: - python >=3.10 license: Unlicense purls: - - pkg:pypi/filelock?source=compressed-mapping + - pkg:pypi/filelock?source=hash-mapping run_exports: {} size: 78622 timestamp: 1787521663311 @@ -14785,24 +14763,25 @@ packages: run_exports: {} size: 19501 timestamp: 1761594405382 -- conda: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.3-pyhd8ed1ab_0.conda - sha256: 4f84ffdc5471236e8225db86c7508426b46aa2c3802d58ca40b3c3e174533b39 - md5: 9b0d29067484a8dfacfae85b8fba81bc +- conda: https://conda.anaconda.org/conda-forge/noarch/flask-3.1.3-pyhcf101f3_1.conda + sha256: 3980dfba1e3900106cc3e6210294e73f50d02a67fdfe7b3bb36b2721ba9379cb + md5: 156398929bf849da6df8f89a2c390185 depends: - - blinker >=1.6.2 + - python >=3.10 + - blinker >=1.9.0 - click >=8.1.3 - - importlib-metadata >=3.6.0 - - itsdangerous >=2.1.2 + - itsdangerous >=2.2.0 - jinja2 >=3.1.2 - - python >=3.8 - - werkzeug >=2.3.7 + - markupsafe >=2.1.1 + - werkzeug >=3.1.0 + - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/flask?source=hash-mapping run_exports: {} - size: 79782 - timestamp: 1692686247131 + size: 87428 + timestamp: 1771489274528 - conda: https://conda.anaconda.org/conda-forge/noarch/flask-cors-6.0.2-pyhcf101f3_0.conda sha256: 0fbd9cf74d82ef1956366117ba93192f4ea8e661c55b2a31672ca20de7fb07eb md5: ce9c8b23fcfda2754226cec5975a1b31 @@ -14859,20 +14838,6 @@ packages: run_exports: {} size: 17075 timestamp: 1774748034564 -- conda: https://conda.anaconda.org/conda-forge/noarch/flask-login-0.6.3-pyhd8ed1ab_2.conda - sha256: d50d005b15de19cfafde744cd1e7c371416db2c2300b6b848db5e5f53fdde6eb - md5: b040ac9fef7176cc5fff85a5982fdc74 - depends: - - flask >=1.0.4 - - python >=3.9 - - werkzeug >=1.0.1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/flask-login?source=hash-mapping - run_exports: {} - size: 20663 - timestamp: 1733924557151 - conda: https://conda.anaconda.org/conda-forge/noarch/flask-mail-0.10.0-pyhd8ed1ab_0.conda sha256: 5930b02cb48670cd4fcb42a02229f75e8ce80e7eb7177ff3f89fd396d36e9222 md5: 99f6efe5bd77173d9101fa5a78e79968 @@ -15139,7 +15104,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/gitpython?source=compressed-mapping + - pkg:pypi/gitpython?source=hash-mapping run_exports: {} size: 177303 timestamp: 1786443183227 @@ -15165,7 +15130,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/h11?source=compressed-mapping + - pkg:pypi/h11?source=hash-mapping run_exports: {} size: 39069 timestamp: 1767729720872 @@ -15578,7 +15543,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jinja2?source=compressed-mapping + - pkg:pypi/jinja2?source=hash-mapping run_exports: {} size: 120685 timestamp: 1764517220861 @@ -15603,7 +15568,7 @@ packages: - python >=3.10 license: MIT purls: - - pkg:pypi/jplephem?source=compressed-mapping + - pkg:pypi/jplephem?source=hash-mapping run_exports: {} size: 41950 timestamp: 1772477050871 @@ -15701,7 +15666,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markdown?source=compressed-mapping + - pkg:pypi/markdown?source=hash-mapping size: 85893 timestamp: 1770694658918 - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.3-pyhcf101f3_0.conda @@ -16023,7 +15988,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/platformdirs?source=compressed-mapping + - pkg:pypi/platformdirs?source=hash-mapping run_exports: {} size: 26956 timestamp: 1786708411066 @@ -16036,7 +16001,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/platformdirs?source=compressed-mapping + - pkg:pypi/platformdirs?source=hash-mapping size: 25643 timestamp: 1771233827084 - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -16075,7 +16040,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pooch?source=compressed-mapping + - pkg:pypi/pooch?source=hash-mapping run_exports: {} size: 56833 timestamp: 1769816568869 @@ -16273,7 +16238,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyjwt?source=compressed-mapping + - pkg:pypi/pyjwt?source=hash-mapping size: 30144 timestamp: 1769858771741 - conda: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.13.0-pyhcf101f3_0.conda @@ -16694,7 +16659,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/python-discovery?source=compressed-mapping + - pkg:pypi/python-discovery?source=hash-mapping run_exports: {} size: 38785 timestamp: 1786705670745 @@ -16946,7 +16911,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/requests?source=compressed-mapping + - pkg:pypi/requests?source=hash-mapping size: 63602 timestamp: 1766926974520 - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda @@ -16980,7 +16945,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/rich?source=compressed-mapping + - pkg:pypi/rich?source=hash-mapping size: 208472 timestamp: 1771572730357 - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -17057,7 +17022,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/setuptools?source=compressed-mapping + - pkg:pypi/setuptools?source=hash-mapping size: 637506 timestamp: 1770634745653 - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda @@ -17170,7 +17135,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/sniffio?source=compressed-mapping + - pkg:pypi/sniffio?source=hash-mapping run_exports: {} size: 15698 timestamp: 1762941572482 @@ -17462,7 +17427,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/tomli?source=compressed-mapping + - pkg:pypi/tomli?source=hash-mapping size: 21453 timestamp: 1768146676791 - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda @@ -17703,7 +17668,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/uvicorn?source=compressed-mapping + - pkg:pypi/uvicorn?source=hash-mapping run_exports: {} size: 56796 timestamp: 1783518931535 @@ -17720,7 +17685,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/uvicorn?source=compressed-mapping + - pkg:pypi/uvicorn?source=hash-mapping run_exports: {} size: 57699 timestamp: 1783518883165 @@ -17737,7 +17702,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/uvicorn?source=compressed-mapping + - pkg:pypi/uvicorn?source=hash-mapping run_exports: {} size: 60189 timestamp: 1787133003107 @@ -17829,7 +17794,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/virtualenv?source=compressed-mapping + - pkg:pypi/virtualenv?source=hash-mapping run_exports: {} size: 3894937 timestamp: 1786427893830 @@ -17853,19 +17818,34 @@ packages: run_exports: {} size: 61391 timestamp: 1759928175142 -- conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.8-pyhd8ed1ab_0.conda - sha256: ce0c68c3dc44e106dd15b24534c085a2c9bf6603227983d01c2bad41edcad864 - md5: 4d0a38559ef093d9987ad2934e535473 +- conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.8-pyhcf101f3_0.conda + sha256: 5108c1bf2f0512e5c9dc8191e31b144c23b7cf0e73423a246173006002368d79 + md5: 3eeca039e7316268627f4116da9df64c depends: - markupsafe >=2.1.1 - - python >=3.8 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/werkzeug?source=hash-mapping run_exports: {} - size: 255168 - timestamp: 1699492638827 + size: 258232 + timestamp: 1775160081069 +- conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.9-pyh5ded981_0.conda + sha256: 9bb5c06955efec7a5801bb39a06896f22026c39d845b9fc8ade15e6861ef9284 + md5: cb4014ab1e412ad4a8aeeee11577065e + depends: + - markupsafe >=2.1.1 + - python >=3.11 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/werkzeug?source=compressed-mapping + run_exports: {} + size: 260273 + timestamp: 1790550578594 - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f md5: 46e441ba871f524e2b067929da3051c2 @@ -17986,7 +17966,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/xarray?source=compressed-mapping + - pkg:pypi/xarray?source=hash-mapping size: 1011911 timestamp: 1771083999178 - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda @@ -18355,7 +18335,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/coverage?source=compressed-mapping + - pkg:pypi/coverage?source=hash-mapping size: 396141 timestamp: 1770720638049 - conda: https://conda.anaconda.org/conda-forge/osx-64/cryptography-43.0.3-py311h968b48f_0.conda @@ -20007,7 +19987,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 25987 timestamp: 1772445597250 - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-3.10.8-py311h6eed73b_0.conda @@ -20061,7 +20041,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/multidict?source=compressed-mapping + - pkg:pypi/multidict?source=hash-mapping size: 89614 timestamp: 1771611130785 - conda: https://conda.anaconda.org/conda-forge/osx-64/nco-5.3.6-h7dddbad_0.conda @@ -20342,7 +20322,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=compressed-mapping + - pkg:pypi/pandas?source=hash-mapping size: 14527714 timestamp: 1771409062806 - conda: https://conda.anaconda.org/conda-forge/osx-64/pango-1.56.4-h6ef8af8_0.conda @@ -20665,7 +20645,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping + - pkg:pypi/pyyaml?source=hash-mapping size: 194735 timestamp: 1770223769285 - conda: https://conda.anaconda.org/conda-forge/osx-64/qhull-2020.2-h3c5361c_5.conda @@ -20783,7 +20763,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping size: 15306181 timestamp: 1771880807305 - conda: https://conda.anaconda.org/conda-forge/osx-64/sdl2-2.32.56-h53ec75d_0.conda @@ -22437,7 +22417,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 23871 timestamp: 1772445652936 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py310hb6292c7_0.conda @@ -22526,7 +22506,7 @@ packages: - python_abi 3.10.* *_cp310 license: MIT purls: - - pkg:pypi/netcdf4?source=compressed-mapping + - pkg:pypi/netcdf4?source=hash-mapping size: 1062885 timestamp: 1772476344532 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nspr-4.38-heaf21c2_0.conda @@ -22868,7 +22848,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping + - pkg:pypi/pyyaml?source=hash-mapping size: 163966 timestamp: 1770223747482 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda @@ -24943,7 +24923,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 29362 timestamp: 1772445178723 - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.9.1-py311h1ea47a8_1.conda @@ -25014,7 +24994,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/multidict?source=compressed-mapping + - pkg:pypi/multidict?source=hash-mapping size: 92622 timestamp: 1771610838436 - conda: https://conda.anaconda.org/conda-forge/win-64/nco-5.3.6-h7142fb5_3.conda @@ -25057,7 +25037,7 @@ packages: - hdf5 >=1.14.6,<1.14.7.0a0 license: MIT purls: - - pkg:pypi/netcdf4?source=compressed-mapping + - pkg:pypi/netcdf4?source=hash-mapping size: 1056331 timestamp: 1772476013884 - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py311h80b3fa1_1.conda @@ -25536,7 +25516,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping + - pkg:pypi/pyyaml?source=hash-mapping size: 187108 timestamp: 1770223467913 - conda: https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda @@ -25639,7 +25619,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping size: 15280874 timestamp: 1771881523220 - conda: https://conda.anaconda.org/conda-forge/win-64/sdl2-2.32.56-h5112557_0.conda @@ -26058,5 +26038,5 @@ packages: purls: [] size: 388453 timestamp: 1764777142545 -- pypi: . +- pypi: ./ name: open-mss diff --git a/pixi.toml b/pixi.toml index 4ea984835..f301afc7a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -16,10 +16,9 @@ defusedxml = "*" email_validator = "*" execnet = "*" fastkml = ">=1.1.0,<2" -flask = ">=2.3.2" +flask = ">=3.1.3" flask-cors = "*" flask-httpauth = "*" -flask-login = "*" flask-mail = "*" flask-migrate = "*" flask-socketio = ">=5.1.0" @@ -64,7 +63,7 @@ skyfield-data = ">=7" tk = "*" unicodecsv = "*" websocket-client = "*" -werkzeug = ">=2.2.3, <3.0.0" +werkzeug = ">=3.1.5" xstatic = "<2.0.0" xstatic-bootstrap = ">=5" xstatic-jquery = "*" @@ -82,6 +81,10 @@ libxmlsec1 = "*" dbus-python = "*" libxmlsec1 = "*" +[target.win-64.dependencies] +# 3.1.9 fixes safe_join with Windows device names (GHSA-g6x2-hccm-hh4m); chat attachments and the gallery use it +werkzeug = ">=3.1.9" + [feature.addons.dependencies] geographiclib = "*" geomag = "*" diff --git a/tests/_test_mscolab/test_server.py b/tests/_test_mscolab/test_server.py index f2f03dc26..7da3dc3b7 100644 --- a/tests/_test_mscolab/test_server.py +++ b/tests/_test_mscolab/test_server.py @@ -198,8 +198,8 @@ def test_delete_user(self): def test_upload_endpoints_exist(self): # the upload limit applies to these endpoints by name, a renamed view would get the larger limit - from mslib.mscolab.app import MSColabRequest - assert set(MSColabRequest.UPLOAD_ENDPOINTS) <= set(self.app.view_functions) + from mslib.mscolab.app import UPLOAD_ENDPOINTS + assert set(UPLOAD_ENDPOINTS) <= set(self.app.view_functions) assert current_app.config['MAX_CONTENT_LENGTH'] > current_app.config['MAX_UPLOAD_SIZE'] def test_operation_larger_than_upload_limit(self): @@ -218,6 +218,59 @@ def test_operation_larger_than_upload_limit(self): assert response.get_json() == {"success": False, "message": f"Request too large. The limit is {limit:.1f} MiB."} + @pytest.mark.parametrize("content_type", ["application/x-www-form-urlencoded", "multipart/form-data"]) + def test_operation_larger_than_form_memory_size(self, content_type): + # Werkzeug limits a form field to MAX_FORM_MEMORY_SIZE (500 kB), the flight track of an operation + # is a form field and may be as large as the whole request. Werkzeug 3.1.9 limits only multipart forms, so + # the multipart case guards the fix on every platform; msui sends urlencoded forms, which Werkzeug 3.1.5 to + # 3.1.8 limit too. + assert add_user(self.userdata[0], self.userdata[1], self.userdata[2], self.userdata[3]) + padding = "x" * (current_app.config['MAX_FORM_MEMORY_SIZE'] + 1) + large = XML_CONTENT_INIT.replace("", f"", 1) + with self.app.test_client() as test_client: + response = test_client.post('/token', data={"email": self.userdata[0], "password": self.userdata[2]}) + token = json.loads(response.data.decode("utf-8"))["token"] + response = test_client.post('/create_operation', content_type=content_type, + data={"token": token, "path": "largeflight", "description": "d", + "content": large}) + assert response.status_code == 200 + assert Operation.query.filter_by(path="largeflight").first() is not None + + def test_form_fields_of_other_endpoints_keep_the_form_memory_size(self): + # only the flight track of a new operation may be larger, not a field sent to /token by anyone + large = "x" * (current_app.config['MAX_FORM_MEMORY_SIZE'] + 1) + with self.app.test_client() as test_client: + response = test_client.post('/token', content_type="multipart/form-data", + data={"email": large, "password": "x"}) + assert response.status_code == 413 + assert response.get_json() == { + "success": False, + "message": "A form field of the request is too large, or the request has too many form parts."} + + def test_too_many_form_parts(self): + # the request is small, the message must not claim a size limit + data = {f"field{i}": "x" for i in range(current_app.config['MAX_FORM_PARTS'] + 1)} + with self.app.test_client() as test_client: + response = test_client.post('/token', content_type="multipart/form-data", data=data) + assert response.status_code == 413 + assert "too many form parts" in response.get_json()["message"] + + def test_without_max_content_length(self, monkeypatch): + # MAX_CONTENT_LENGTH None: no limit for the request, Flask's MAX_FORM_MEMORY_SIZE for a form field, + # and a 413 is answered with JSON, not with an error in the handler + monkeypatch.setitem(current_app.config, 'MAX_CONTENT_LENGTH', None) + assert add_user(self.userdata[0], self.userdata[1], self.userdata[2], self.userdata[3]) + padding = "x" * (current_app.config['MAX_FORM_MEMORY_SIZE'] + 1) + large = XML_CONTENT_INIT.replace("", f"", 1) + with self.app.test_client() as test_client: + operation, token = self._create_operation(test_client, self.userdata) + assert operation is not None + response = test_client.post('/create_operation', content_type="multipart/form-data", + data={"token": token, "path": "largeflight", "description": "d", + "content": large}) + assert response.status_code == 413 + assert response.get_json()["success"] is False + def test_oversized_uploads_refused(self): assert add_user(self.userdata[0], self.userdata[1], self.userdata[2], self.userdata[3]) oversized = b"x" * (current_app.config['MAX_UPLOAD_SIZE'] + 1)