ImportError: cannot import name 'url_decode' from 'werkzeug.urls'
Asked Answered
A

7

14

I am building a webapp using Flask. I imported the flask-login library to handle user login. But it shows an ImportError.

Below is my folder structure:

>flask_blog1
    >flaskblog
        >static
        >templates
        >__init__.py
        >forms.py
        >models.py
        >routes.py
    >instance
        >site.db
    >venv
    >requirements.txt
    >run.py

My run.py:

from flaskblog import app

if __name__ == "__main__":
    app.run(debug=True)

My __init__.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager

app = Flask(__name__)
app.config["SECRET_KEY"] = "5791628bb0b13ce0c676dfde280ba245"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///site.db"
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)

from flaskblog import routes

My models.py:

from datetime import datetime

# from .extensions import db
from flaskblog import db, login_manager
from flask_login import UserMixin


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    image_file = db.Column(db.String(20), nullable=False, default="default.jpg")
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship("Post", backref="author", lazy=True)

    def __repr__(self):
        return f"User('{self.username}', '{self.email}', '{self.image_file}')"


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

    def __repr__(self):
        return f"Post('{self.title}', '{self.date_posted}')"

My routes.py:

from flask import render_template, flash, redirect, url_for
from flaskblog import app, db, bcrypt
from flaskblog.forms import RegistrationForm, LoginForm
from flaskblog.models import User, Post
from flask_login import login_user

posts = [
    {
        "author": "Ashutosh Chapagain",
        "title": "Blog Post 1",
        "content": "First Post Content",
        "date_posted": "October 1, 2023",
    },
    {
        "author": "Ash Dhakal",
        "title": "Blog Post 2",
        "content": "Second Post Content",
        "date_posted": "October 2, 2023",
    },
]


@app.route("/")
@app.route("/home")
def home():
    return render_template("home.html", posts=posts)


@app.route("/about")
def about():
    return render_template("about.html", title="About")


@app.route("/register", methods=["GET", "POST"])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
            "utf-8"
        )
        user = User(
            username=form.username.data, email=form.email.data, password=hashed_password
        )
        db.session.add(user)
        db.session.commit()
        flash(f"Your account has been created! You are now able to log in!", "success")
        return redirect(url_for("login"))
    return render_template("register.html", title="Register", form=form)


@app.route("/login", methods=["GET", "POST"])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and bcrypt.check_password_hash(user.password, form.password.data):
            login_user(user, remember=form.remember.data)
            return redirect(url_for("home"))
        else:
            flash("Login Unsuccessful. Please check email and password", "danger")
    return render_template("login.html", title="Login", form=form)

My forms.py:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flaskblog.models import User


class RegistrationForm(FlaskForm):
    username = StringField(
        "Username", validators=[DataRequired(), Length(min=2, max=20)]
    )
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Password", validators=[DataRequired()])
    confirm_password = PasswordField(
        "Confirm Password", validators=[DataRequired(), EqualTo("password")]
    )
    submit = SubmitField("Sign Up")

    def validate_username(self, username):
        user = User.query.filter_by(username=username.data).first()
        if user:
            raise ValidationError(
                "That username is taken. Please choose a different one."
            )

    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user:
            raise ValidationError("That email is taken. Please choose a different one.")


class LoginForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Password", validators=[DataRequired()])
    remember = BooleanField("Remember Me")
    submit = SubmitField("Login")

The exact error is:

(venv) asu@asu-Lenovo-Legion-5-15ARH05:/media/asu/Data/Projects/flask_blog1$ python3 run.py
Traceback (most recent call last):
  File "/media/asu/Data/Projects/flask_blog1/run.py", line 1, in <module>
    from flaskblog import app
  File "/media/asu/Data/Projects/flask_blog1/flaskblog/__init__.py", line 4, in <module>
    from flask_login import LoginManager
  File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/__init__.py", line 12, in <module>
    from .login_manager import LoginManager
  File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/login_manager.py", line 33, in <module>
    from .utils import _create_identifier
  File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/utils.py", line 14, in <module>
    from werkzeug.urls import url_decode
ImportError: cannot import name 'url_decode' from 'werkzeug.urls' (/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/werkzeug/urls.py)
Adah answered 2/10, 2023 at 11:7 Comment(2)
Backwards compatibility? Not to be expected here. I quit using Flask for this instability.Complex
Fix is here https://mcmap.net/q/827754/-cannot-import-name-39-url_decode-39-from-39-werkzeug-urls-39Fowkes
K
27

I can only assume you got the Werkzeug 3.0 update (as flask-login didn't up-bound their werkzeug dependency).

In their ongoing quest to remove all the non-core public APIs of werkzeug, the developers deprecated most of werkzeug.urls in Werkzeug 2.3 (released April 25th 2023), and removed it in Werkzeug 3.0 (released September 30th 2023).

Your options are:

Karlakarlan answered 2/10, 2023 at 12:11 Comment(5)
I forced werkzeug to version 2.3 and it werked.Omnipotent
You can try using the version from the development repository directly: pip install git+https://github.com/maxcountryman/flask-login.git - Last commit message is: 'flask 3.0 compatibility'.Tambratamburlaine
git+https://github.com/maxcountryman/flask-login.git@7d98a49bc38d0849367b348bfe37a2b689323419Tambratamburlaine
Miguel Grinberg wrote about that issue on his blog: blog.miguelgrinberg.com/post/we-have-to-talk-about-flaskStrasser
You can use following versions, they work together: Werkzeug==3.0.0 Flask-Login==0.6.3 Flask==3.0.0Peril
A
9

The problem was as described by @Masklinn. I fixed the problem by changing the versions of werkzeug and flask in my requirements.py and reinstalling them.

My original requirements.txt:

bcrypt==4.0.1
blinker==1.6.2
click==8.1.7
dnspython==2.4.2
email-validator==2.0.0.post2
Flask==3.0.0
Flask-Bcrypt==1.0.1
Flask-Login==0.6.2
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.0
greenlet==2.0.2
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
SQLAlchemy==2.0.21
typing_extensions==4.8.0
Werkzeug==3.0.0
WTForms==3.0.1

My modified requirements.txt:

bcrypt==4.0.1
blinker==1.6.2
click==8.1.7
dnspython==2.4.2
email-validator==2.0.0.post2
Flask==2.3.0
Flask-Bcrypt==1.0.1
Flask-Login==0.6.2
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.1
greenlet==2.0.2
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
SQLAlchemy==2.0.21
typing_extensions==4.8.0
Werkzeug==2.3.0
WTForms==3.0.1
Adah answered 2/10, 2023 at 13:22 Comment(1)
Just want to add that in case you're using poetry, the change would be implemented in the relevant pyproject.toml as ``` [tool.poetry.dependencies] werkzeug= "2.2.3" ``` or whatever version you're forcing it toEsquibel
B
1

Not an answer, but context:

The OP was following a Flask tutorial on YouTube, "Python Flask Tutorial: Full-Featured Web App Part 6 - User Authentication". I believe the tutorial was written in 2017/2018, and today is Oct 2023.

The OP was having a problem authenticating users right after doing a PIP install of flask_login. I had the same problem and was direct to this post after doing a search of the error ImportError: cannot import name 'url_decode' from 'werkzeug.urls'

I was able to get around the problem by uninstallig the existing version of werkzeug, followed by installing an older version of werkzeug.

$ pip uninstall werkzeug
$ pip install werkzeug==2.3.0
Beecher answered 17/10, 2023 at 22:39 Comment(0)
P
1

As others have highlighted, the issue is with backward compatibility with Werkzeug 3.0 update. With the new release of Flask-login to version 0.6.3 on Oct 30, 2023, this issue is fixed.

The following version of packages work fine together:

Werkzeug==3.0.0
Flask-Login==0.6.3
Flask==3.0.0
Peril answered 16/2 at 11:51 Comment(0)
B
0

It seems the issue is in fact fixed, but not on pypi yet. https://github.com/maxcountryman/flask-login/issues/805

This issue is causing problems for users who want to use Flask-Login with Flask 3.0. The fix has been implemented, but it needs to be deployed to PyPI to ensure users get the correct version. The solution might involve triggering deployment on PyPI.

I simply installed it directly from github.

pip install git+https://github.com/maxcountryman/flask-login.git

This allows you to use the most recent Flask and Flask-Security-Too.

Brierroot answered 19/10, 2023 at 10:31 Comment(0)
E
0

Better not downgrade due to CVE's. Have you checked Flask-WTF==1.2.1? I have upped to this version in order to fix this problem.

Epochmaking answered 28/5 at 9:21 Comment(0)
J
-3

I got the same error today, seems like those functions don't exist anymore

Jiggered answered 2/10, 2023 at 12:1 Comment(1)
This post belongs in the comment section.Vespucci

© 2022 - 2024 — McMap. All rights reserved.