Skip to content

Move to Python 3.13, bump Werkzeug to 3.1.8, and refresh/pin all dependencies - #22

Merged
Paul Lizer (paullizer) merged 3 commits into
mainfrom
dependabot/pip/api/werkzeug-3.1.6
Aug 20, 2026
Merged

Move to Python 3.13, bump Werkzeug to 3.1.8, and refresh/pin all dependencies#22
Paul Lizer (paullizer) merged 3 commits into
mainfrom
dependabot/pip/api/werkzeug-3.1.6

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 20, 2026

Copy link
Copy Markdown
Contributor

Why this changed shape

Dependabot bumped Werkzeug 2.2.2 → 3.1.6 in /api but left Flask==2.2.2 in place. As originally proposed, this PR would have taken the API down.

Flask 2.2.2 does from werkzeug.urls import url_quote at module scope, and url_quote was removed in Werkzeug 3.0. Flask 2.2.2 declares Werkzeug>=2.2.2 with no upper bound, so pip resolves the combination happily and the image builds fine — then gunicorn dies on boot:

ImportError: cannot import name 'url_quote' from 'werkzeug.urls'

The bump is still worth doing — Werkzeug 2.2.2 carries debugger-PIN RCE, multipart DoS, cookie-parsing, and Windows safe_join CVEs. It just needed Flask carried along with it. Scope was then widened to refresh and pin everything, and to move all services to Python 3.13.

Python 3.13

File From To
api/Dockerfile python:3.11-slim python:3.13-slim
front_end/Dockerfile python:3.11-slim python:3.13-slim
task/Dockerfile azure-functions/python:4-python3.11 4-python3.13
front-end-tests.yml (both jobs) 3.11 3.13
prod-changes_byavdlinuxbrokerapi.yml 3.11 3.13

This unblocks azure-functions 1.25.0 → 2.2.0, which requires Python >=3.13 — the sole reason the task app was previously held back. azure-functions 2.0 ships the same SDK content as 1.25.0b3, so there are no programming-model changes; it only requires Python ≥3.13 and Functions runtime 4.x. The bicep already sets FUNCTIONS_EXTENSION_VERSION: '~4' and the function app is container-deployed, so no infrastructure change was needed.

Dependency changes

api/requirements.txt

Package From To
Flask 2.2.2 3.1.3
Werkzeug 3.1.6 3.1.8
gunicorn unpinned 26.0.0
azure-monitor-opentelemetry 1.8.7 1.8.9
azure-identity 1.17.1 1.25.3
azure-mgmt-compute 33.0.0 38.3.0
pyjwt 2.9.0 2.13.0
cryptography 43.0.3 50.0.0
requests 2.26.0 2.34.2
flask_caching 2.3.0 2.4.1
pymssql 2.3.1 2.3.13
azure-keyvault-secrets 4.9.0 4.11.1

front_end/requirements.txt — was still entirely on Flask/Werkzeug 2.2.2 and shared the same latent break. Flask → 3.1.3, Werkzeug → 3.1.8, gunicorn pinned 26.0.0, azure-monitor-opentelemetry → 1.8.9, requests → 2.34.2, Flask-WTF 1.2.1 → 1.3.0.

task/requirements.txt — everything was unpinned. Now azure-functions==2.2.0, requests==2.34.2, azure-identity==1.25.3, with azure.identity normalised to azure-identity.

Devpytest==9.1.1 for both services, PyYAML==6.0.3.

Every pin was re-resolved against Python 3.13. Versions reflect what is currently retrievable from the internal package feed, which is intentional.

Verification

All on Python 3.13.15:

  • 122 tests pass — 44 api, 78 front_end — executed the same way CI does: a clean pip install -r requirements.txt -r requirements-dev.txt on a bare image with no system packages, confirming the cp313 wheels for pymssql and cryptography resolve without build tooling.
  • All three images build on their real base images.
  • front_end boots under gunicorn → /health 200, /favicon.ico 200 (exercises send_from_directory, the safe_join CVE surface)
  • api boots under gunicorn → binds, /health returns a clean 503 without a database, unknown routes 404
  • task function app loads and registers all 3 timer triggers

Compatibility notes

No removed Flask 2.x APIs (before_first_request, JSONEncoder, _app_ctx_stack) are used anywhere. Werkzeug 3.1's new 500 kB form-field cap is a non-issue — the api is JSON-only and front_end's form fields are small scalars with no uploads. The azure-mgmt-compute 33 → 38 jump was checked directly: virtual_machines.begin_start and begin_power_off are both still present.

Follow-up worth filing

front_end/function_authentication.py uses datetime.utcnow().timestamp(), which now emits a DeprecationWarning on 3.13 and is scheduled for removal. It is also subtly incorrect — utcnow() returns a naive datetime and .timestamp() interprets naive values as local time, so it only produces the right epoch on a UTC host. Harmless in these containers, but worth fixing outside a dependency PR since it touches token-expiry logic.

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file python Pull requests that update python code labels Aug 20, 2026
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 2.2.2 to 3.1.6.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](pallets/werkzeug@2.2.2...3.1.6)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-version: 3.1.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot
dependabot Bot force-pushed the dependabot/pip/api/werkzeug-3.1.6 branch from 1467ca1 to 88e1cae Compare August 20, 2026 11:10
The Dependabot change bumped Werkzeug 2.2.2 -> 3.1.6 in /api but left
Flask==2.2.2 in place. Flask 2.2.2 does `from werkzeug.urls import url_quote`
at module scope, and url_quote was removed in Werkzeug 3.0. Flask 2.2.2
declares `Werkzeug>=2.2.2` with no upper bound, so pip resolves it and the
image builds -- then gunicorn dies at startup with:

    ImportError: cannot import name 'url_quote' from 'werkzeug.urls'

The only workflow triggers on pushes to prod-changes rather than on pull
requests, so this would not have been caught before deploy.

api:
  Flask 2.2.2 -> 3.1.3, Werkzeug 3.1.6 -> 3.1.8, gunicorn now pinned, and
  Azure SDK / requests / cryptography / pyjwt / flask-caching refreshed.

front_end:
  Same Flask/Werkzeug bump (it was still entirely on 2.2.2 and shared the
  latent break), plus Flask-WTF 1.2.1 -> 1.3.0.

task:
  Previously unpinned deps are now pinned, and `azure.identity` is normalised
  to `azure-identity`. azure-functions is held at 1.25.0 because 2.x requires
  Python >=3.13 while the image is python3.11.

dev:
  pytest 9.1.1 for both services, PyYAML 6.0.3.

Verified: 122 tests pass (44 api, 78 front_end); all three services build on
their real base images for linux/python3.11 and boot under gunicorn --
front_end returns 200 on /health and /favicon.ico (exercising
send_from_directory, the safe_join CVE surface), api returns a clean 503 on
/health without a database and 404s unknown routes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer Paul Lizer (paullizer) changed the title Bump werkzeug from 2.2.2 to 3.1.6 in /api Bump Werkzeug to 3.1.8 and refresh/pin all Python dependencies Aug 20, 2026
azure-functions 2.x requires Python >=3.13, which was the reason the task app
was held back at azure-functions 1.25.0. Moving the runtime to 3.13 unblocks it.

- api/Dockerfile, front_end/Dockerfile: python:3.11-slim -> python:3.13-slim
- task/Dockerfile: azure-functions/python:4-python3.11 -> 4-python3.13
- Both workflows (app tests + prod deploy): python-version 3.11 -> 3.13
- task: azure-functions 1.25.0 -> 2.2.0

azure-functions 2.0 carries the same SDK content as 1.25.0b3, so there are no
programming-model changes; the only requirements are Python >=3.13 and Functions
runtime 4.x. The bicep already sets FUNCTIONS_EXTENSION_VERSION '~4' and the
function app is container-deployed, so no infrastructure change is needed.

Every other pin was re-resolved against Python 3.13 and was already at the
newest version available, so no other requirement changed.

Verified on Python 3.13.15:
- 122 tests pass (44 api, 78 front_end), run the same way CI does -- a clean
  `pip install -r requirements.txt -r requirements-dev.txt` on a bare image with
  no system packages, confirming the cp313 wheels for pymssql and cryptography
  resolve.
- All three images build on their real base images.
- api and front_end boot under gunicorn: front_end returns 200 on /health and
  /favicon.ico, api binds and returns a clean 503 on /health without a database.
- task function app loads and registers all 3 timer triggers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer Paul Lizer (paullizer) changed the title Bump Werkzeug to 3.1.8 and refresh/pin all Python dependencies Move to Python 3.13, bump Werkzeug to 3.1.8, and refresh/pin all dependencies Aug 20, 2026
@paullizer
Paul Lizer (paullizer) merged commit c0e3f68 into main Aug 20, 2026
9 checks passed
@dependabot
dependabot Bot deleted the dependabot/pip/api/werkzeug-3.1.6 branch August 20, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant