Upgrade tooling on the project.

- Replace black by ruff, as it's quicker ;
- Use `uv` wherever possible as a replacement for pip, as it's way faster to run, add an `uv.lock` file which will be synced before the releases and published here ;
- Remove tox, it's too complex for this project and can easily be replaced by `uv` ;
- Apply `ruff` formatting ;
- Update the makefile accordingly ;
- Update the CI accordingly
This commit is contained in:
Alexis Métaireau 2024-09-29 23:47:48 +02:00
parent cf77b4c346
commit 6e31a9c8b5
15 changed files with 1935 additions and 170 deletions

View file

@ -1,26 +0,0 @@
name: Check doc
on:
push:
branches: [ 'master', 'stable-*' ]
pull_request:
branches: [ 'master', 'stable-*' ]
jobs:
test_doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Check we can generate documentation
run: tox -e docs

View file

@ -1,4 +1,4 @@
name: Lint & unit tests name: CI
on: on:
push: push:
@ -11,18 +11,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Install uv and set the python version
uses: actions/setup-python@v4 uses: astral-sh/setup-uv@v4
with: with:
python-version: "3.11" python-version: "3.11"
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Run Lint - name: Run Lint
run: tox -e lint run: make lint
test: test:
# Dependency on linting to avoid running our expensive matrix test for nothing # Dependency on linting to avoid running our expensive matrix test for nothing
@ -56,7 +50,8 @@ jobs:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
dependencies: [normal] dependencies: [normal]
database: [sqlite] database: [sqlite]
# Test other databases with only a few versions of Python (Debian bullseye has 3.9, bookworm has 3.11) # Test other databases with only a few versions of Python
# (Debian bullseye has 3.9, bookworm has 3.11)
include: include:
- python-version: 3.9 - python-version: 3.9
dependencies: normal dependencies: normal
@ -95,32 +90,36 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Install uv and set the python version
uses: actions/setup-python@v4 uses: astral-sh/setup-uv@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Change dependencies to minimal supported versions - name: Change dependencies to minimal supported versions
run: sed -i -e 's/>=/==/g; s/~=.*==\(.*\)/==\1/g; s/~=/==/g;' pyproject.toml # This sed comment installs the minimal supported version
# for all versions except for "requires-python"
# This is to detect that the minimum versions are really
# supported, in the CI
run: sed -i -e '/requires-python/!s/>=/==/g; /requires-python/!s/~=.*==\(.*\)/==\1/g; /requires-python/!s/~=/==/g;' pyproject.toml
if: matrix.dependencies == 'minimal' if: matrix.dependencies == 'minimal'
- name: Install dependencies - name: Run tests
run: | run: make test
python -m pip install --upgrade pip
python -m pip install tox
# Run tox using the version of Python in `PATH`
- name: Run Tox with sqlite
run: tox -e py
if: matrix.database == 'sqlite'
env: env:
TESTING_SQLALCHEMY_DATABASE_URI: 'sqlite:///budget.db' # Setup the DATABASE_URI depending on the matrix we are using.
- name: Run Tox with postgresql TESTING_SQLALCHEMY_DATABASE_URI: ${{
run: tox -e py matrix.database == 'sqlite'
if: matrix.database == 'postgresql' && 'sqlite:///budget.db'
env: || matrix.database == 'postgresql'
TESTING_SQLALCHEMY_DATABASE_URI: 'postgresql+psycopg2://postgres:ihatemoney@localhost:5432/ihatemoney_ci' && 'postgresql+psycopg2://postgres:ihatemoney@localhost:5432/ihatemoney_ci'
- name: Run Tox with mariadb || 'mysql+pymysql://root:ihatemoney@localhost:3306/ihatemoney_ci'
run: tox -e py }}
if: matrix.database == 'mariadb'
env: docs:
TESTING_SQLALCHEMY_DATABASE_URI: 'mysql+pymysql://root:ihatemoney@localhost:3306/ihatemoney_ci' runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v4
with:
python-version: "3.11"
- name: Build docs
run: make build-docs

View file

@ -5,7 +5,10 @@ This document describes changes between each past release.
## 6.2.0 (unreleased) ## 6.2.0 (unreleased)
- Add support for python 3.12 (#757) - Add support for python 3.12 (#757)
- Remove support for python 3.7
- Replace the black linter by ruff
- Replace virtualenv and pip by uv
- Remove tox
## 6.1.5 (2024-03-19) ## 6.1.5 (2024-03-19)

View file

@ -1,60 +1,40 @@
VIRTUALENV = python3 -m venv VIRTUALENV = uv venv
SPHINX_BUILDDIR = docs/_build
VENV := $(shell realpath $${VIRTUAL_ENV-.venv}) VENV := $(shell realpath $${VIRTUAL_ENV-.venv})
PYTHON = $(VENV)/bin/python3 BIN := uv tool run
PIP := uv pip
PYTHON = $(BIN)/python3
DEV_STAMP = $(VENV)/.dev_env_installed.stamp DEV_STAMP = $(VENV)/.dev_env_installed.stamp
INSTALL_STAMP = $(VENV)/.install.stamp INSTALL_STAMP = $(VENV)/.install.stamp
TEMPDIR := $(shell mktemp -d) TEMPDIR := $(shell mktemp -d)
ZOPFLIPNG := zopflipng ZOPFLIPNG := zopflipng
MAGICK_MOGRIFY := mogrify MAGICK_MOGRIFY := mogrify
.PHONY: all
all: install ## Alias for install
.PHONY: install
install: virtualenv pyproject.toml $(INSTALL_STAMP) ## Install dependencies
$(INSTALL_STAMP):
$(VENV)/bin/pip install -U pip
$(VENV)/bin/pip install -e .
touch $(INSTALL_STAMP)
.PHONY: virtualenv .PHONY: virtualenv
virtualenv: $(PYTHON) virtualenv: $(PYTHON)
$(PYTHON): $(PYTHON):
$(VIRTUALENV) $(VENV) $(VIRTUALENV) $(VENV)
.PHONY: install-dev
install-dev: virtualenv pyproject.toml $(INSTALL_STAMP) $(DEV_STAMP) ## Install development dependencies
$(DEV_STAMP): $(PYTHON)
$(VENV)/bin/pip install -Ue .[dev]
touch $(DEV_STAMP)
.PHONY: remove-install-stamp
remove-install-stamp:
rm $(INSTALL_STAMP)
.PHONY: update
update: remove-install-stamp install ## Update the dependencies
.PHONY: serve .PHONY: serve
serve: install build-translations ## Run the ihatemoney server serve: build-translations ## Run the ihatemoney server
@echo 'Running ihatemoney on http://localhost:5000' @echo 'Running ihatemoney on http://localhost:5000'
FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi $(VENV)/bin/flask run --host=0.0.0.0 FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi uv run flask run --host=0.0.0.0
.PHONY: test .PHONY: test
test: install-dev ## Run the tests test:
$(VENV)/bin/tox uv run --extra dev --extra database pytest .
.PHONY: black .PHONY: lint
black: install-dev ## Run the tests lint:
$(VENV)/bin/black --target-version=py37 . uv tool run ruff check .
uv tool run vermin --no-tips --violations -t=3.8- .
.PHONY: isort .PHONY: format
isort: install-dev ## Run the tests format:
$(VENV)/bin/isort . uv tool run ruff format .
.PHONY: release .PHONY: release
release: install-dev ## Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release) release: # Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release)
$(VENV)/bin/fullrelease uv run --extra dev fullreleas
.PHONY: compress-showcase .PHONY: compress-showcase
compress-showcase: compress-showcase:
@ -72,27 +52,30 @@ compress-assets: compress-showcase ## Compress static assets
.PHONY: build-translations .PHONY: build-translations
build-translations: ## Build the translations build-translations: ## Build the translations
$(VENV)/bin/pybabel compile -d ihatemoney/translations uv run --extra dev pybabel compile -d ihatemoney/translations
.PHONY: extract-translations .PHONY: extract-translations
extract-translations: ## Extract new translations from source code extract-translations: ## Extract new translations from source code
$(VENV)/bin/pybabel extract --add-comments "I18N:" --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney uv run --extra dev pybabel extract --add-comments "I18N:" --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney
$(VENV)/bin/pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/ uv run --extra dev pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/
.PHONY: create-database-revision .PHONY: create-database-revision
create-database-revision: ## Create a new database revision create-database-revision: ## Create a new database revision
@read -p "Please enter a message describing this revision: " rev_message; \ @read -p "Please enter a message describing this revision: " rev_message; \
$(PYTHON) -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}" uv run python -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: create-empty-database-revision .PHONY: create-empty-database-revision
create-empty-database-revision: ## Create an empty database revision create-empty-database-revision: ## Create an empty database revision
@read -p "Please enter a message describing this revision: " rev_message; \ @read -p "Please enter a message describing this revision: " rev_message; \
$(PYTHON) -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}" uv run python -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: clean .PHONY: clean
clean: ## Destroy the virtual environment clean: ## Destroy the virtual environment
rm -rf .venv rm -rf .venv
build-docs:
uv run --extra doc sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html
.PHONY: help .PHONY: help
help: ## Show the help indications help: ## Show the help indications
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View file

@ -22,7 +22,7 @@ highly encouraged to do so.
## Requirements ## Requirements
- **Python**: version 3.7 to 3.12. - **Python**: version 3.8 to 3.12.
- **Backends**: SQLite, PostgreSQL, MariaDB (version 10.3.2 or above), - **Backends**: SQLite, PostgreSQL, MariaDB (version 10.3.2 or above),
Memory. Memory.

View file

@ -183,7 +183,7 @@ We are using [black](https://black.readthedocs.io/en/stable/) and
Python files in this project. Be sure to run it locally on your files. Python files in this project. Be sure to run it locally on your files.
To do so, just run: To do so, just run:
make black isort make lint
You can also integrate them with your dev environment (as a You can also integrate them with your dev environment (as a
*format-on-save* hook, for instance). *format-on-save* hook, for instance).

View file

@ -90,7 +90,6 @@ def get_billform_for(project, set_default=True, **kwargs):
class CommaDecimalField(DecimalField): class CommaDecimalField(DecimalField):
"""A class to deal with comma in Decimal Field""" """A class to deal with comma in Decimal Field"""
def process_formdata(self, value): def process_formdata(self, value):

View file

@ -103,7 +103,7 @@ def validate_configuration(app):
if "MAIL_DEFAULT_SENDER" not in app.config: if "MAIL_DEFAULT_SENDER" not in app.config:
app.config["MAIL_DEFAULT_SENDER"] = default_settings.DEFAULT_MAIL_SENDER app.config["MAIL_DEFAULT_SENDER"] = default_settings.DEFAULT_MAIL_SENDER
if type(app.config["MAIL_DEFAULT_SENDER"]) == tuple: if type(app.config["MAIL_DEFAULT_SENDER"]) is tuple:
(name, address) = app.config["MAIL_DEFAULT_SENDER"] (name, address) = app.config["MAIL_DEFAULT_SENDER"]
app.config["MAIL_DEFAULT_SENDER"] = f"{name} <{address}>" app.config["MAIL_DEFAULT_SENDER"] = f"{name} <{address}>"
warnings.warn( warnings.warn(

View file

@ -9,7 +9,6 @@ from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
class TestAPI(IhatemoneyTestCase): class TestAPI(IhatemoneyTestCase):
"""Tests the API""" """Tests the API"""
def api_create( def api_create(

View file

@ -1030,9 +1030,7 @@ class TestBudget(IhatemoneyTestCase):
assert """<thead> assert """<thead>
<tr> <tr>
<th>Project</th> <th>Project</th>
<th>Number of participants</th>""" in resp.data.decode( <th>Number of participants</th>""" in resp.data.decode("utf-8")
"utf-8"
)
def test_dashboard_project_deletion(self): def test_dashboard_project_deletion(self):
self.post_project("raclette") self.post_project("raclette")

View file

@ -84,7 +84,6 @@ def flash_email_error(error_message, category="danger"):
class Redirect303(HTTPException, RoutingException): class Redirect303(HTTPException, RoutingException):
"""Raise if the map requests a redirect. This is for example the case if """Raise if the map requests a redirect. This is for example the case if
`strict_slashes` are activated and an url that requires a trailing slash. `strict_slashes` are activated and an url that requires a trailing slash.
@ -102,7 +101,6 @@ class Redirect303(HTTPException, RoutingException):
class PrefixedWSGI(object): class PrefixedWSGI(object):
""" """
Wrap the application in this middleware and configure the Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind front-end server to add these headers, to let you quietly bind

View file

@ -8,6 +8,7 @@ Basically, this blueprint takes care of the authentication and provides
some shortcuts to make your life better when coding (see `pull_project` some shortcuts to make your life better when coding (see `pull_project`
and `add_project_id` for a quick overview) and `add_project_id` for a quick overview)
""" """
import datetime import datetime
from functools import wraps from functools import wraps
import hashlib import hashlib

View file

@ -5,6 +5,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "ihatemoney" name = "ihatemoney"
version = "6.2.0.dev0" version = "6.2.0.dev0"
requires-python = ">=3.7"
description = "A simple shared budget manager web application." description = "A simple shared budget manager web application."
readme = "README.md" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}
@ -15,7 +16,6 @@ keywords = ["web", "budget"]
classifiers = [ classifiers = [
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
@ -30,6 +30,7 @@ dependencies = [
"cachetools>=4.1,<5", "cachetools>=4.1,<5",
"debts>=0.5,<1", "debts>=0.5,<1",
"email_validator>=1.0,<3", "email_validator>=1.0,<3",
"Flask>=2,<3",
"Flask-Babel>=1.0,<4", "Flask-Babel>=1.0,<4",
"Flask-Cors>=3.0.8,<4", "Flask-Cors>=3.0.8,<4",
"Flask-Limiter>=2.6,<3", "Flask-Limiter>=2.6,<3",
@ -39,37 +40,33 @@ dependencies = [
"Flask-SQLAlchemy>=2.4,<3", "Flask-SQLAlchemy>=2.4,<3",
"Flask-Talisman>=0.8,<2", "Flask-Talisman>=0.8,<2",
"Flask-WTF>=0.14.3,<2", "Flask-WTF>=0.14.3,<2",
"WTForms>=2.3.3,<3.2",
"Flask>=2,<3",
"Werkzeug>=2,<3",
"itsdangerous>=2,<3", "itsdangerous>=2,<3",
"Jinja2>=3,<4", "Jinja2>=3,<4",
"pytest-libfaketime[dev]>=0.1.3",
"python-dateutil",
"qrcode>=7.1,<8", "qrcode>=7.1,<8",
"requests>=2.25,<3", "requests>=2.25,<3",
"SQLAlchemy-Continuum>=1.3.12,<2", "SQLAlchemy>=1.3.0,<1.5",
"SQLAlchemy>=1.3.0,<1.5", # New 1.4 changes API, see #728 "SQLAlchemy-Continuum>=1.3.12,<2", # New 1.4 changes API, see #728
"python-dateutil", "Werkzeug>=2,<3",
] "WTForms>=2.3.3,<3.2",]
[project.optional-dependencies] [project.optional-dependencies]
database = [ database = [
# Python 3.11 support starts in 2.9.2 # Python 3.11 support starts in 2.9.2
"psycopg2-binary>=2.9.2,<3", "psycopg2-binary>=2.9.2,<2.9.9",
"PyMySQL>=0.9,<1.1", "PyMySQL>=0.9,<1.1",
] ]
dev = [ dev = [
"black==23.3.0", "ruff==0.6.8",
"flake8==5.0.4", "flake8==5.0.4",
"isort==5.11.5", "isort==5.11.5",
"vermin==1.5.2", "vermin==1.5.2",
"pytest>=6.2.5", "pytest>=6.2.5",
"pytest-flask>=1.2.0", "pytest-flask>=1.2.0",
"pytest-libfaketime>=0.1.2",
"tox>=3.14.6",
"zest.releaser>=6.20.1", "zest.releaser>=6.20.1",
"libfaketime>=2.1.0",
] ]
doc = [ doc = [
"Sphinx>=7.0.1,<8", "Sphinx>=7.0.1,<8",
"docutils==0.20.1", "docutils==0.20.1",
@ -107,3 +104,7 @@ include = [
"README.md", "README.md",
"SECURITY.md", "SECURITY.md",
] ]
[tool.ruff]
exclude = ["ihatemoney/migrations"]

41
tox.ini
View file

@ -1,41 +0,0 @@
[tox]
isolated_build = true
envlist = py312,py311,py310,py39,py38,py37,lint,docs
skip_missing_interpreters = True
[testenv]
passenv = TESTING_SQLALCHEMY_DATABASE_URI
commands =
python --version
py.test --pyargs ihatemoney.tests {posargs}
deps =
-e.[database,dev]
# To be sure we are importing ihatemoney pkg from pip-installed version
changedir = /tmp
[testenv:lint]
commands =
black --check --target-version=py37 .
isort --check --profile black .
flake8 ihatemoney
vermin --no-tips --violations -t=3.7- .
deps =
-e.[dev]
changedir = {toxinidir}
[testenv:docs]
commands =
sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html
deps =
-e.[doc]
changedir = {toxinidir}
[flake8]
exclude = migrations
max_line_length = 100
extend-ignore =
# See https://github.com/PyCQA/pycodestyle/issues/373
E203,

1851
uv.lock Normal file

File diff suppressed because it is too large Load diff