Skip to content

Flask 2 Upgrade#1077

Merged
catinhere merged 23 commits intomasterfrom
task/1016-flask2-upgrade
Apr 24, 2026
Merged

Flask 2 Upgrade#1077
catinhere merged 23 commits intomasterfrom
task/1016-flask2-upgrade

Conversation

@catinhere
Copy link
Copy Markdown
Contributor

@catinhere catinhere commented Apr 23, 2026

Flask 2 Upgrade

Upgrades ComPAIR from Flask 1.1.2 to Flask 2.3.3 and modernizes the dependency stack.

Dependencies

  • Flask 1.1.2 → 2.3.3

  • flask-restfulflask-restx 1.3.0 — flask-restful is incompatible with Werkzeug 2.x (a Flask 2 dependency) due to breaking changes in Werkzeug's internals; flask-restx is the community-maintained fork that supports Werkzeug 2.x and has an otherwise compatible API.

  • Werkzeug 2.0.3 → 2.3.8 — required by Flask 2.3.3 (https://flask.palletsprojects.com/en/stable/changes/#version-2-3-3)

  • Jinja2 3.0.3 → 3.1.4 — required by Flask 2.3.3; previously pinned to 3.0.3 due to a Markup breakage that is resolved by moving the import to markupsafe directly (https://flask.palletsprojects.com/en/stable/changes/#version-2-3-0)

  • markupsafe 2.0.1 → 2.1.5 — previously pinned due to Flask 1 compatibility; restriction lifted

  • itsdangerous 2.0.1 → 2.1.2 — required by Flask 2.3.3; previously pinned due to Flask 1 compatibility

  • Flask-Login 0.6.1 → 0.6.3 — 0.6.2 fixed compatibility issues with Werkzeug 2.2 and Flask 2.2 due to breaking changes in Flask's request handling (https://github.com/maxcountryman/flask-login/blob/main/CHANGES.md)

  • blinker 1.4 → 1.9.0 — Flask 2.3.3 requires blinker >= 1.6.2, which introduced async signal support and type hints that Flask 2.3's signals infrastructure depends on (https://flask.palletsprojects.com/en/stable/changes/#version-2-3-0)

  • Removed six — Python 2/3 compatibility library no longer needed as the codebase is Python 3 only

Code changes

  • Replaced all flask_restful imports with flask_restx across all API files
  • Set doc=False on flask-restx Api instances to suppress Swagger UI at blueprint roots (flask-restx enables this by default, unlike flask-restful)
  • Renamed send_file parameter attachment_filenamedownload_name to match Flask 2 API (https://flask.palletsprojects.com/en/stable/changes/#version-2-2-0)
  • Moved Markup import from jinja2 to markupsafe, where it now lives in Flask 2 (https://flask.palletsprojects.com/en/stable/changes/#version-2-3-0)
  • Replaced all six shims with Python 3 builtins (str, io.BytesIO, io.StringIO, functools.wraps, built-in range)
  • Removed manual Python 2/3 urllib compatibility try/except blocks in report.py and lti_membership.py, replaced with direct Python 3 imports
  • Updated ERROR_404_HELP config key to RESTX_ERROR_404_HELP to match flask-restx's renamed setting (rename ERROR_404_HELP to RESTX_ERROR_404_HELP for consistency python-restx/flask-restx#114)
  • Added assertRedirects override to handle Werkzeug 2.1's change to return relative redirect locations instead of absolute URLs
  • Fixed bug where BytesIO.BytesIO() was called incorrectly in profiler code; replaced with StringIO()
  • Removed _unauthorized_override and api.unauthorized assignment
    • dead code since 2015 when @login_manager.unauthorized_handler took over 401 handling
    • flask-restx also does not send the HTTP basic auth challenge by default (serve_challenge_on_401=False), making the override doubly unnecessary

Tests

  • Updated assertions to match flask-restx's error response shape (errors key instead of message)
  • Replaced six.assertCountEqual/six.assertRegex with native unittest equivalents
  • Refactored demo and learning record tests away from blueprint re-registration pattern

Test plan

  • Run full test suite (pytest)
  • Verify file upload and download (download_name rename affects this path)
  • Confirm no Swagger UI appears at any API blueprint root

catinhere and others added 22 commits April 20, 2026 12:56
`test_courses.py`: Flask-RESTX 1.x appends "Must not be null!" to the help string

`test_learning_records.py`: Moved blueprint registration to `setUp` so it runs once on test init rather than mid-test. For the xAPI and Caliper tests, the `ENABLED=False` 404 check and re-enable are moved inside `with self.login(...)` to fix incorrect assertion order (the handler checks auth before the enabled flag, so the 404 path is only reachable when logged in).

`test_demo.py`: Removed mid-test call to `register_demo_api_blueprints` after toggling `DEMO_INSTALLATION`. Removed because re-registration no longer needed because Flask 2 raises an error on duplicate blueprint registration
In Jinja2.3.x (comes with Flask 2), Markup was moved to its own package `markupsafe` and `jinja2.Markup` alias removed.
`send_file` parameter `attachment_filename` was renamed to `download_name` in v2.0 and fully deprecated in v2.2.0.
`pstats.Stats.print_stats()` writes text strings (not bytes) to output stream by calling `stream.write(str_value)`. Passing `BytesIO` will raise a `TypeError` the moment profile stats are flushed.
…ternative for Flask-Testing, unused imports
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR upgrades ComPAIR from Flask 1.1.2 to Flask 2.3.3 and modernizes the full dependency stack, including the migration from flask-restful to the community-maintained flask-restx fork and removal of the Python 2/3 compatibility library six.

Key changes:

  • All flask_restful imports replaced with flask_restx across 25+ API files; doc=False added to all Api instances to suppress the Swagger UI that flask-restx enables by default
  • send_file parameter renamed attachment_filenamedownload_name in compair/api/__init__.py and its test mock assertions
  • Markup import moved from jinja2 to markupsafe; ERROR_404_HELP config key renamed to RESTX_ERROR_404_HELP
  • All six shims (text_type, BytesIO, StringIO, wraps, range, six.moves.urllib) replaced with Python 3 builtins across ~20 files
  • Test parser-validation error assertions updated: flask-restx places field errors under errors (not nested under message) and appends \" Must not be null!\" to the help string for nullable=False violations
  • Demo and learning-record tests refactored away from mid-test blueprint re-registration — Flask 2 raises ValueError on duplicate blueprint registration, so blueprints are now registered once in setUp
  • Pre-existing bug fixed: profiled() in compair/api/util/__init__.py called BytesIO.BytesIO() (invalid); replaced with io.StringIO(), which is correct since pstats.Stats writes text output

Confidence Score: 4/5

PR is on the happy path to merge; prior thread concerns are resolved and no new critical bugs are present

The migration is thorough and consistent across all 54 files. All previously flagged concerns (assertRedirects semantics, api.unauthorized dead code, learning-record test coverage) have been addressed in the thread. The test assertions are correctly updated for flask-restx's error response shape. The only remaining risk is runtime behavioral differences between flask-restful and flask-restx that won't surface until the full test suite runs.

No files require special attention; all mechanical changes are consistent. The full pytest run and manual verification of login flows and file download paths listed in the test plan are the remaining gates before merge.

Important Files Changed

Filename Overview
requirements.txt Dependency versions updated: Flask 2.3.3, Werkzeug 2.3.8, blinker 1.9.0, flask-restx 1.3.0, markupsafe 2.1.5, itsdangerous 2.1.2; removed six and pinning comments that were Flask 1-era workarounds
compair/api/util/init.py Removed six imports and dead _unauthorized_override; fixed pre-existing BytesIO.BytesIO() bug by switching to StringIO() for pstats output; new_restful_api now returns Api(blueprint, doc=False) to suppress Swagger UI
compair/tests/test_compair.py Added assertRedirects override for Werkzeug 2.1 relative redirect behavior; moved register_demo_api_blueprints into ComPAIRAPIDemoTestCase.setUp to avoid mid-test blueprint re-registration that Flask 2 disallows; fixed wraps import from functools instead of six
compair/api/init.py Renamed send_file parameter attachment_filename → download_name for Flask 2 API; updated flask_restful reqparser import to flask_restx
compair/settings.py Renamed ERROR_404_HELP → RESTX_ERROR_404_HELP to match flask-restx's configuration key
compair/init.py Moved Markup import from jinja2 to markupsafe, where it now lives in Flask 2 / Jinja2 3.1+
compair/tests/api/test_learning_records.py Refactored to call register_learning_record_api_blueprints in setUp instead of mid-test; moved ENABLED=False checks inside login block since @login_required fires first, making the unauthenticated path unreachable
compair/tests/api/test_courses.py Updated two parser validation error assertions to use rv.json["errors"] key (flask-restx format) and the new "Must not be null!" suffix; application-level abort() message assertions left unchanged
compair/models/lti_models/lti_membership.py Removed six, urlparse, and urlencode imports (none were used); removed Python 2/3 urllib try/except block; text_type replaced with str()
compair/core.py Removed six dependency; text_type replaced with str(); added missing newline at end of file

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[HTTP Request] --> B[Flask 2.3.3 Router]
    B --> C{Blueprint matched?}
    C -->|/api/*| D[flask-restx Api\ndoc=False]
    C -->|/api/learning_records/*| E[learning_record_api\nregistered in setUp]
    C -->|/api/demo/*| F[demo_api\nregistered in setUp]
    D --> G{login_required?}
    E --> G
    F --> G
    G -->|not logged in| H[flask-login\nunauthorized_handler → 401]
    G -->|logged in| I[Resource.get / post / put / delete]
    I --> J{reqparse validation}
    J -->|nullable=False violated| K[abort 400\nerrors key in response]
    J -->|valid| L[Business logic]
    L -->|abort with message| M[HTTPException\ne.data message key]
    L -->|success| N[marshal response]
    K --> O[JSON: errors + message]
    M --> P[JSON: message + title]
    N --> Q[JSON response to client]
Loading

Reviews (2): Last reviewed commit: "Remove #1016: Dead code _unauthorized_ov..." | Re-trigger Greptile

Comment thread compair/tests/api/test_learning_records.py
Comment thread compair/tests/test_compair.py
Comment thread compair/api/util/__init__.py Outdated
@catinhere
Copy link
Copy Markdown
Contributor Author

@greptileai given the responses to your comments, review pr again

Copy link
Copy Markdown
Member

@xcompass xcompass left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@catinhere catinhere merged commit 553346c into master Apr 24, 2026
8 checks passed
@catinhere catinhere deleted the task/1016-flask2-upgrade branch April 24, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants