Error [cannot import name 'url_quote' from 'werkzeug.urls']

ImportError: cannot import name 'url_quote' from 'werkzeug.urls' の直し方(Flask が新しい Werkzeug で落ちる)

FIX SUMMARY verified
Applies when
python:3.11-slimFlask 2.0.3 x Werkzeug 3.0 (url_quote removed)loose Werkzeug>=2.0 constraint pulls Werkzeug 3.x; Flask 2.0.3 imports removed werkzeug.urls.url_quote

Verified: reproduced in python:3.11-slim, then the cannot import name 'url_quote' from 'werkzeug.urls' signature was gone after the fix (exit 0).

Flask のアプリを起動した瞬間、import flask の時点で次のエラーが出て止まることがあります。

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

あなたのコードは何も変えていないのに、import flask(や Flask を読み込む最初の行)で落ちます。原因は Flask 本体でも、あなたのアプリでもありません。Flask が内部で使っている werkzeug.urls.url_quote が、新しい Werkzeug で削除されたためです。Flask と Werkzeug の版が食い違い、Flask が「もう無い名前」を読み込もうとしています。

急いで直すなら、Werkzeug を Flask に合う版に固定するか、Flask を新しい版に上げるのどちらかです。

なぜ起きるのか:Flask が読む名前が Werkzeug から消えた

Flask は Werkzeug の上に作られていて、起動時に Werkzeug の関数をいくつか読み込みます。url_quote はそのひとつです。Werkzeug はこの url_quote を非推奨にしたのち、メジャーバージョンを上げたときに werkzeug.urls から削除しました。古い Flask は、まだ削除前の名前を読み込むコードのままです。

そのため、古い Flask に新しい Werkzeug を組み合わせると、Flask が存在しない名前を import しようとして落ちます。エラーが ImportError: cannot import name 'url_quote' という形なのは、名前そのものが Werkzeug 側に無くなっているからです。

なぜ手元では出ないのか

多くの場合、引き金は Werkzeug のバージョンです。Flask だけをバージョン指定してインストールすると、Werkzeug は指定しないぶん、そのとき入手できる新しい版が一緒に入ることがあります。

# Flask だけ固定して入れると、Werkzeug は新しい版が入りうる
pip install "Flask==2.0.3"

以前に環境を作ったときは、まだ Werkzeug が古かったので動いていました。ところが新しい venv を作り直した、CI でクリーンにインストールした、requirements.txt に Werkzeug を書いていない、といった状況で最新の Werkzeug が入ると、同じ Flask が落ちるようになります。**「手元(古い Werkzeug が残っている環境)では動くのに、作り直した環境や CI でだけ落ちる」**というすれ違いです。まず pip show werkzeug flask で、両方の版を突き合わせてください。

直し方:版を合わせる

方向は2つあります。

Werkzeug を、その Flask に合う版に固定する。 応急処置としては、削除前の Werkzeug に留めます。

pip install "Flask==2.0.3" "Werkzeug<3"

requirements.txt を使っているなら、Flask だけでなく Werkzeug も明示的に固定します。Flask の版に対して、動いていた Werkzeug の版を書いておけば、環境を作り直しても同じ組み合わせが入ります。固定する版には、なぜその版に留めているか(たとえば Flask を上げるまでの暫定)を一言添えておくと、後で解除の判断がしやすくなります。

Flask を新しい版に上げる。 新しい Flask は url_quote への依存をやめ、対応する Werkzeug の範囲を自分で指定しています。Werkzeug を古いままにしておく理由が特に無いなら、Flask を上げるほうが後を引きません。

pip install --upgrade "Flask"

どちらを選ぶかは、Flask を上げてほかの依存やアプリのコードに影響が出ないかで決めます。上げられない事情があるなら Werkzeug を固定し、そうでなければ Flask を上げて Werkzeug も対応版にそろえます。

pip はなぜ止めてくれないのか

「バージョンの食い違いなら、pip install の時点で止めてくれてもよさそうなのに」と思うかもしれません。止まらないのは、Flask 側の Werkzeug への要求に上限が無いからです。古い Flask は Werkzeug を「ある版以上」とだけ要求していて、「この版より上はだめ」という上限を書いていません。そのため pip から見ると、新しい Werkzeug は要求を満たす正当な選択で、衝突とは判定されません。install は成功し、壊れているとわかるのは実行時です。

これは、依存の指定がきつすぎて pip が止まる ResolutionImpossible とは逆の失敗です。あちらは pip が衝突を検出して install を止めます(ResolutionImpossible の直し方)。こちらは指定が緩すぎて pip が素通しし、実行時に落ちます。pip の依存解決は、上限の無い要求までは守れません。だから、実行時互換に敏感な組み合わせ(Flask と Werkzeug、requestsurllib3 など)は、片方を上げたら相手の版も見て、必要なら自分で上限や固定を書き足すことになります。

切り分け(うまくいかないとき)

  • url_quote ではなく別の名前で同じ ImportError が出るcannot import name 'BaseResponse' など、Werkzeug が削除した別の名前でも同型のエラーになります。原因は同じで、古い Flask(や Flask 拡張)が新しい Werkzeug の消えた名前を読んでいます。対処も同じで、Werkzeug を合う版に固定するか、読み込んでいる側を上げます。
  • Flask ではなく Flask 拡張(Flask-SQLAlchemy など)で出た:拡張も Werkzeug や Flask の内部を読み込むことがあり、同じ版ズレで落ちます。落ちている import 文が指すパッケージを上げるか、Werkzeug/Flask の版をその拡張が対応する範囲にそろえます。
  • pip install は通ったのに実行時に落ちる、を先に検知したいpip check を実行すると、入っている構成に既知の非互換が無いかを確認できます。ただし今回のように上限の無い要求が原因だと、pip check でも「矛盾なし」と出ることがあります。最終的には、実際に import する起動テストが確実です。
  • 一度 Werkzeug<3 で固定したら、別のパッケージが新しい Werkzeug を要求して衝突した:今度は ResolutionImpossible 側の問題です。その要求元も含めて、全体で両立する版の組み合わせを探すことになります。

検証環境

  • python:3.11-slim、ネットワーク有り
  • 再現:pip install "Flask==2.0.3" "Werkzeug==3.0.0" の後、import flaskImportError: cannot import name 'url_quote' from 'werkzeug.urls' で終了コード 1
  • 修正:pip install "Flask==2.0.3" "Werkzeug==2.0.3" の後、同じスクリプトが終了コード 0

再現から修正までは errfix の検証ハーネスが機械的に確認しています。Flask 2.0.3 に Werkzeug 3.0 を組み合わせると import flask が落ちること、Werkzeug を互換版に固定すると同じコードが通ることを、再現→修正→シグネチャ消滅として通しました。url_quote は Werkzeug 2.3.8 では存在し(非推奨)、3.0.0 で削除されていることも実測しています。Flask 側がこの import をやめた版など、上流の変更履歴に関わる点は Flask・Werkzeug の公式情報で確認しています。

検証(machine-verified)

この修正は python:3.11-slim のバージョン固定コンテナ内で再現し、修正後に cannot import name 'url_quote' from 'werkzeug.urls' のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/werkzeug-url-quote-importerror
● reproduce cannot import name 'url_quote' from 'werkzeug.urls' present ✓
● apply fix exit 0
● re-run cannot import name 'url_quote' from 'werkzeug.urls' gone ✓
PASS verified · python:3.11-slim · signature gone