diff --git a/.editorconfig b/.editorconfig index 8b942d4d..bbe7dde5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -32,3 +32,7 @@ indent_size = 2 # Dockerfile [Dockerfile] indent_size = 4 + +# Align TOML indenting with Tombi +[*.toml] +indent_size = 2 diff --git a/.github/workflows/i18n-push.yml b/.github/workflows/i18n-push.yml index 8c35ab45..0e9bff8b 100644 --- a/.github/workflows/i18n-push.yml +++ b/.github/workflows/i18n-push.yml @@ -13,4 +13,5 @@ jobs: uses: inveniosoftware/invenio-i18n/.github/workflows/i18n-push-base.yml@master with: extract-backend: true + backend-package-path: flask_security secrets: inherit diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index b46a6b8b..18ebb02f 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -17,3 +17,4 @@ jobs: secrets: inherit with: babel-compile-catalog: true + backend-package-path: flask_security diff --git a/.gitignore b/.gitignore index 2585c622..fc872ced 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ Session.vim .venv +uv.lock diff --git a/.tx/config b/.tx/config index 397816aa..e106a331 100644 --- a/.tx/config +++ b/.tx/config @@ -6,9 +6,9 @@ # # 1) Create message catalog: -# $ python setup.py extract_messages -# $ python setup.py init_catalog -l -# $ python setup.py compile_catalog +# $ pybabel extract -F pyproject.toml -o flask_security/translations/messages.pot flask_security +# $ pybabel init -i flask_security/translations/messages.pot -d flask_security/translations -l +# $ pybabel compile -d flask_security/translations --use-fuzzy # 2) Ensure project has been created on Transifex under the inveniosoftware # organisation. # 3) Install the transifex-client diff --git a/CHANGES b/CHANGES index a0bf14dc..f570029c 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,14 @@ Flask-Security-Invenio Changelog Here you can see the full list of changes between each Flask-Security-Invenio release. +Version 4.2.2 +------------- + +Released July 2nd 2026 + +- chore(setup): migrate build backend from setuptools to hatchling +- chore(format): reformat with black + Version 4.2.1 ------------- diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c5bc48b3..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,28 +0,0 @@ -exclude .coverage tests/.coverage -exclude flask_security/translations/pwl.txt -include .editorconfig -include *.sh -include AUTHORS -include babel.ini -include CHANGES -include LICENSE -include pytest.ini -include README.rst -include requirements.txt -include tox.ini -prune scripts -recursive-exclude docs/_build * -recursive-exclude tests/__pycache__ * -recursive-include artwork *.svg -recursive-include docs *.html -recursive-include docs *.inc -recursive-include docs *.png -recursive-include docs *.py -recursive-include docs *.rst -recursive-include docs *.txt -recursive-include docs Makefile -recursive-include flask_security/templates *.* -recursive-include flask_security/translations *.po *.pot *.mo -recursive-include tests * -include .git-blame-ignore-revs -include .tx/config diff --git a/babel.ini b/babel.ini deleted file mode 100644 index b5610521..00000000 --- a/babel.ini +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2019 CERN. -# Copyright (C) 2025 KTH Royal Institute of Technology. -# - -# Extraction from Python source files -[python: **.py] -encoding = utf-8 - -# Extraction from Jinja2 templates - -[jinja2: **/templates/**.html] -encoding = utf-8 - -[jinja2: **/templates/**.jinja] -encoding = utf-8 - diff --git a/flask_security/__init__.py b/flask_security/__init__.py index 549b2327..a56630e1 100644 --- a/flask_security/__init__.py +++ b/flask_security/__init__.py @@ -58,7 +58,7 @@ url_for_security, ) -__version__ = "4.2.1" +__version__ = "4.2.2" __all__ = ( "AnonymousUser", "auth_required", diff --git a/flask_security/views.py b/flask_security/views.py index a48473e2..495675b9 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -113,7 +113,7 @@ def register(): return _security.render_template( config_value("REGISTER_USER_TEMPLATE"), register_user_form=form, - **_ctx("register") + **_ctx("register"), ) @@ -131,7 +131,7 @@ def send_confirmation(): return _security.render_template( config_value("SEND_CONFIRMATION_TEMPLATE"), send_confirmation_form=form, - **_ctx("send_confirmation") + **_ctx("send_confirmation"), ) @@ -204,7 +204,7 @@ def forgot_password(): return _security.render_template( config_value("FORGOT_PASSWORD_TEMPLATE"), forgot_password_form=form, - **_ctx("forgot_password") + **_ctx("forgot_password"), ) @@ -246,7 +246,7 @@ def reset_password(token): config_value("RESET_PASSWORD_TEMPLATE"), reset_password_form=form, reset_password_token=token, - **_ctx("reset_password") + **_ctx("reset_password"), ) @@ -269,7 +269,7 @@ def change_password(): return _security.render_template( config_value("CHANGE_PASSWORD_TEMPLATE"), change_password_form=form, - **_ctx("change_password") + **_ctx("change_password"), ) diff --git a/pyproject.toml b/pyproject.toml index ec4e1ea4..f28bc9fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,84 @@ +# SPDX-FileCopyrightText: 2022 CERN. +# SPDX-FileCopyrightText: 2026 TU Wien. +# SPDX-License-Identifier: MIT + +[project] +name = "Flask-Security-Invenio" +description = "Simple security for Flask apps." +readme = "README.rst" +requires-python = ">=3.9" +license = "MIT" +authors = [ + { name = "CERN/TU Wien/JRC", email = "info@inveniosoftware.org" }, +] +keywords = [ + "flask", + "security", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", +] +dependencies = [ + "email-validator>=1.0.5", + "flask>=1.1.4", + "flask-babel>=2.0.0", + "flask-login>=0.4.1", + "flask-mail>=0.9.1", + "flask-principal>=0.4.0", + "flask-wtf>=1.1.0", + "itsdangerous>=2.0", + "libpass>=1.9.0", + "markupsafe>=2.1.5", + "speaklater>=1.3", + "zxcvbn>=4.4.28", +] +dynamic = ["version"] + +[project.urls] +Repository = "https://github.com/inveniosoftware/flask-security-invenio" + +[project.optional-dependencies] +tests = [ + "bcrypt>=3.1.0,<5.0.0", + "coverage>=5.3,<6", + "flask-sphinx-themes>=1.0.2", + "flask-sqlalchemy>=2.5.1", + "mock>=1.3.0", + "msgcheck>=2.9", + "pytest-black-ng>=0.4.0", + "pytest-cov>=2.10.1", + "pytest-flask>=1.0.0", + "pytest-isort>=1.2.0", + "pytest-pycodestyle>=2.2.0", + "pytest-pydocstyle>=2.2.0", + "pytest>=6,<9", + "sphinx>=4.2.0", +] + [build-system] -requires = ["setuptools", "wheel", "babel>2.8"] -build-backend = "setuptools.build_meta" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "flask_security/__init__.py" + +[tool.hatch.build.targets.wheel] +packages = [ + "/flask_security", +] + +[tool.hatch.build.targets.sdist] +include = [ + "/flask_security", +] + +[tool.isort] +profile = "black" + +[tool.pydocstyle] +add_ignore = "D401" + +[tool.pytest.ini_options] +addopts = "--black --isort --cov=flask_security --cov-report=term-missing --cache-clear" +testpaths = "tests flask_security" +live_server_scope = "module" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 0a1e94e0..00000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -addopts = --black --isort --cov=flask_security --cov-report=term-missing --cache-clear -testpaths = tests flask_security diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0e9ba454..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Trick for ReadTheDocs to install all requirements: - --e .[all] diff --git a/run-tests.sh b/run-tests.sh index e9a86954..0816c817 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -13,7 +13,6 @@ set -o errexit # Quit on unbound symbols set -o nounset -python -m check_manifest --ignore ".*-requirements.txt" python -m sphinx.cmd.build -qnN docs docs/_build/html python -m pytest tests_exit_code=$? diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 67347925..00000000 --- a/setup.cfg +++ /dev/null @@ -1,95 +0,0 @@ -[metadata] -name = Flask-Security-Invenio -version = attr: flask_security.__version__ -description = "Simple security for Flask apps." -long_description = file: README.rst, CHANGES.rst -keywords = flask security -license = MIT -author = CERN/TU Wien/JRC -author_email = info@inveniosoftware.org -platforms = any -url = https://github.com/inveniosoftware/flask-security-invenio -classifiers = - Development Status :: 5 - Production/Stable - -[options] -include_package_data = True -packages = find: -python_requires = >=3.6 -zip_safe = False -install_requires = - email-validator>=1.0.5 - speaklater>=1.3 - Flask-Babel>=2.0.0 - Flask-Login>=0.4.1 - Flask-Mail>=0.9.1 - Flask-Principal>=0.4.0 - Flask-WTF>=1.1.0 - Flask>=1.1.4 - itsdangerous>=2.0 - libpass>=1.9.0 - markupsafe>=2.1.5 - zxcvbn>=4.4.28 - -[options.extras_require] -tests = - # bcrypt 5.0.0+ introduced stricter validation that requires passwords to be - # <= 72 bytes before hashing. This breaks passlib's bcrypt backend initialization - # which tests compatibility by attempting to verify a 255-byte password. - # Error: "ValueError: password cannot be longer than 72 bytes, truncate manually" - # See: https://github.com/pyca/bcrypt/blob/main/CHANGELOG.rst (5.0.0 release notes) - # Passlib needs to be updated to handle this, or we need to migrate away from passlib. - bcrypt>=3.1.0,<5.0.0 - check-manifest>=0.42 - coverage>=5.3,<6 - Flask-Sphinx-Themes>=1.0.2 - Flask-SQLAlchemy>=2.5.1 - mock>=1.3.0 - msgcheck>=2.9 - pytest-black-ng>=0.4.0 - pytest-cov>=2.10.1 - pytest-flask>=1.0.0 - pytest-isort>=1.2.0 - pytest-pycodestyle>=2.2.0 - pytest-pydocstyle>=2.2.0 - pytest>=6,<9 - sphinx>=4.2.0 - -[bdist_wheel] -universal=1 - -[build_sphinx] -source-dir = docs/ -build-dir = docs/_build -warning-is-error = 1 - -[upload_sphinx] -upload-dir = docs/_build/html - -[compile_catalog] -directory = flask_security/translations/ -use_fuzzy = True -domain = flask_security - -[extract_messages] -copyright_holder = CERN -msgid_bugs_address = info@inveniosoftware.org -mapping_file = babel.ini -output_file = flask_security/translations/flask_security.pot -add_comments = NOTE - -[init_catalog] -domain = flask_security -input_file = flask_security/translations/flask_security.pot -output_dir = flask_security/translations/ - -[update_catalog] -domain = flask_security -input_file = flask_security/translations/flask_security.pot -output_dir = flask_security/translations/ - -[pydocstyle] -add_ignore = D401 - -[isort] -profile=black diff --git a/setup.py b/setup.py deleted file mode 100755 index fd2922af..00000000 --- a/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Simple security for Flask apps.""" - -from setuptools import setup - -setup() diff --git a/tests/test_hashing.py b/tests/test_hashing.py index 7e77a71f..c03f81e3 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -20,7 +20,7 @@ def test_verify_password_bcrypt_double_hash(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_SINGLE_HASH": False, - } + }, ) with app.app_context(): assert verify_password("pass", hash_password("pass")) @@ -34,7 +34,7 @@ def test_verify_password_bcrypt_single_hash(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": None, "SECURITY_PASSWORD_SINGLE_HASH": True, - } + }, ) with app.app_context(): assert verify_password("pass", hash_password("pass")) @@ -49,7 +49,7 @@ def test_verify_password_bcrypt_rounds_too_low(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_HASH_OPTIONS": {"bcrypt": {"rounds": 3}}, - } + }, ) assert all(s in str(exc_msg) for s in ["rounds", "too low"]) @@ -62,7 +62,7 @@ def test_login_with_bcrypt_enabled(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_SINGLE_HASH": False, - } + }, ) response = authenticate(app.test_client(), follow_redirects=True) assert b"Home Page" in response.data @@ -77,5 +77,5 @@ def test_missing_hash_salt_option(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": None, "SECURITY_PASSWORD_SINGLE_HASH": False, - } + }, ) diff --git a/tests/test_misc.py b/tests/test_misc.py index 7aca72cf..e2b01cc7 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -143,7 +143,7 @@ def test_addition_identity_attributes(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, - **{"SECURITY_USER_IDENTITY_ATTRIBUTES": ("email", "username")} + **{"SECURITY_USER_IDENTITY_ATTRIBUTES": ("email", "username")}, ) client = app.test_client() response = authenticate(client, email="matt", follow_redirects=True) @@ -174,7 +174,7 @@ def test_change_hash_type(app, sqlalchemy_datastore): "SECURITY_PASSWORD_HASH": "plaintext", "SECURITY_PASSWORD_SALT": None, "SECURITY_PASSWORD_SCHEMES": ["bcrypt", "plaintext"], - } + }, ) app.config["SECURITY_PASSWORD_HASH"] = "bcrypt"