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、requests と urllib3 など)は、片方を上げたら相手の版も見て、必要なら自分で上限や固定を書き足すことになります。
切り分け(うまくいかないとき)
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 flaskがImportError: 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 の公式情報で確認しています。