From 54ff9b297276a3add08c8faf1557b5e359b3ab99 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 9 Nov 2023 09:20:27 -0800 Subject: [PATCH] use ruff linter and formatter --- .flake8 | 25 ---------------- .gitignore | 29 +++++-------------- .pre-commit-config.yaml | 29 ++++--------------- examples/celery/pyproject.toml | 3 ++ examples/javascript/js_example/views.py | 2 +- examples/javascript/pyproject.toml | 3 ++ examples/tutorial/flaskr/__init__.py | 5 ++-- examples/tutorial/flaskr/auth.py | 2 +- examples/tutorial/flaskr/blog.py | 4 +-- examples/tutorial/pyproject.toml | 3 ++ pyproject.toml | 21 ++++++++++++++ src/flask/app.py | 9 ++++-- src/flask/helpers.py | 5 ++-- src/flask/json/provider.py | 4 +-- src/flask/sansio/app.py | 7 +++-- src/flask/sessions.py | 3 +- src/flask/views.py | 1 - tests/test_appctx.py | 9 ++---- .../test_apps/subdomaintestmodule/__init__.py | 1 - tests/test_basic.py | 24 ++++++++------- tests/test_blueprints.py | 9 +++--- tests/test_signals.py | 4 +-- tests/test_testing.py | 11 +++---- tests/test_views.py | 4 +-- tests/typing/typing_app_decorators.py | 6 ++-- tests/typing/typing_route.py | 4 +-- 26 files changed, 99 insertions(+), 128 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8f3b4fd4bc..0000000000 --- a/.flake8 +++ /dev/null @@ -1,25 +0,0 @@ -[flake8] -extend-select = - # bugbear - B - # bugbear opinions - B9 - # implicit str concat - ISC -extend-ignore = - # slice notation whitespace, invalid - E203 - # line length, handled by bugbear B950 - E501 - # bare except, handled by bugbear B001 - E722 - # zip with strict=, requires python >= 3.10 - B905 - # string formatting opinion, B028 renamed to B907 - B028 - B907 -# up to 88 allowed by bugbear B950 -max-line-length = 80 -per-file-ignores = - # __init__ exports names - src/flask/__init__.py: F401 diff --git a/.gitignore b/.gitignore index e6713351c1..83aa92e56c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,10 @@ -.DS_Store -.env -.flaskenv -*.pyc -*.pyo -env/ -venv/ -.venv/ -env* -dist/ -build/ -*.egg -*.egg-info/ -.tox/ -.cache/ -.pytest_cache/ .idea/ -docs/_build/ -.vscode - -# Coverage reports -htmlcov/ +.vscode/ +__pycache__/ +.tox/ .coverage .coverage.* -*,cover +htmlcov/ +docs/_build/ +dist/ +venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00293c575b..c262399a74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,16 @@ ci: - autoupdate_branch: "2.3.x" autoupdate_schedule: monthly repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.3 hooks: - - id: pyupgrade - args: ["--py38-plus"] - - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 - hooks: - - id: reorder-python-imports - name: Reorder Python imports (src, tests) - files: "^(?!examples/)" - args: ["--application-directories", "src"] - - repo: https://github.com/psf/black - rev: 23.7.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear - - flake8-implicit-str-concat + - id: ruff + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: + - id: check-merge-conflict + - id: debug-statements - id: fix-byte-order-marker - id: trailing-whitespace - id: end-of-file-fixer diff --git a/examples/celery/pyproject.toml b/examples/celery/pyproject.toml index cd39467854..25887ca2f7 100644 --- a/examples/celery/pyproject.toml +++ b/examples/celery/pyproject.toml @@ -12,3 +12,6 @@ build-backend = "flit_core.buildapi" [tool.flit.module] name = "task_app" + +[tool.ruff] +src = ["src"] diff --git a/examples/javascript/js_example/views.py b/examples/javascript/js_example/views.py index 0d4b656131..9f0d26c5e1 100644 --- a/examples/javascript/js_example/views.py +++ b/examples/javascript/js_example/views.py @@ -2,7 +2,7 @@ from flask import render_template from flask import request -from js_example import app +from . import app @app.route("/", defaults={"js": "fetch"}) diff --git a/examples/javascript/pyproject.toml b/examples/javascript/pyproject.toml index 6c4d7def6b..0ec631d2c5 100644 --- a/examples/javascript/pyproject.toml +++ b/examples/javascript/pyproject.toml @@ -27,3 +27,6 @@ filterwarnings = ["error"] [tool.coverage.run] branch = true source = ["js_example", "tests"] + +[tool.ruff] +src = ["src"] diff --git a/examples/tutorial/flaskr/__init__.py b/examples/tutorial/flaskr/__init__.py index bb9cce5ab6..e35934d676 100644 --- a/examples/tutorial/flaskr/__init__.py +++ b/examples/tutorial/flaskr/__init__.py @@ -31,12 +31,13 @@ def hello(): return "Hello, World!" # register the database commands - from flaskr import db + from . import db db.init_app(app) # apply the blueprints to the app - from flaskr import auth, blog + from . import auth + from . import blog app.register_blueprint(auth.bp) app.register_blueprint(blog.bp) diff --git a/examples/tutorial/flaskr/auth.py b/examples/tutorial/flaskr/auth.py index b423e6ae4a..34c03a204f 100644 --- a/examples/tutorial/flaskr/auth.py +++ b/examples/tutorial/flaskr/auth.py @@ -11,7 +11,7 @@ from werkzeug.security import check_password_hash from werkzeug.security import generate_password_hash -from flaskr.db import get_db +from .db import get_db bp = Blueprint("auth", __name__, url_prefix="/auth") diff --git a/examples/tutorial/flaskr/blog.py b/examples/tutorial/flaskr/blog.py index 3704626b1c..be0d92c435 100644 --- a/examples/tutorial/flaskr/blog.py +++ b/examples/tutorial/flaskr/blog.py @@ -7,8 +7,8 @@ from flask import url_for from werkzeug.exceptions import abort -from flaskr.auth import login_required -from flaskr.db import get_db +from .auth import login_required +from .db import get_db bp = Blueprint("blog", __name__) diff --git a/examples/tutorial/pyproject.toml b/examples/tutorial/pyproject.toml index 2c806b15ca..73a674cec0 100644 --- a/examples/tutorial/pyproject.toml +++ b/examples/tutorial/pyproject.toml @@ -34,3 +34,6 @@ filterwarnings = ["error"] [tool.coverage.run] branch = true source = ["flaskr", "tests"] + +[tool.ruff] +src = ["src"] diff --git a/pyproject.toml b/pyproject.toml index e4042cb600..652de0da14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,3 +106,24 @@ module = [ "importlib_metadata", ] ignore_missing_imports = true + +[tool.ruff] +src = ["src"] +fix = true +show-fixes = true +show-source = true + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "E", # pycodestyle error + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "W", # pycodestyle warning +] +ignore-init-module-imports = true + +[tool.ruff.lint.isort] +force-single-line = true +order-by-type = false diff --git a/src/flask/app.py b/src/flask/app.py index d710cb96a4..7a1cf4d48b 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1183,7 +1183,8 @@ def make_response(self, rv: ft.ResponseReturnValue) -> Response: # class to the correct type try: rv = self.response_class.force_type( - rv, request.environ # type: ignore[arg-type] + rv, # type: ignore[arg-type] + request.environ, ) except TypeError as e: raise TypeError( @@ -1272,7 +1273,8 @@ def process_response(self, response: Response) -> Response: return response def do_teardown_request( - self, exc: BaseException | None = _sentinel # type: ignore + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] ) -> None: """Called after the request is dispatched and the response is returned, right before the request context is popped. @@ -1305,7 +1307,8 @@ def do_teardown_request( request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) def do_teardown_appcontext( - self, exc: BaseException | None = _sentinel # type: ignore + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] ) -> None: """Called right before the application context is popped. diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 13a5aa219c..8601c2fd87 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -21,6 +21,7 @@ if t.TYPE_CHECKING: # pragma: no cover from werkzeug.wrappers import Response as BaseResponse + from .wrappers import Response @@ -48,9 +49,7 @@ def get_load_dotenv(default: bool = True) -> bool: def stream_with_context( - generator_or_function: ( - t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]] - ) + generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]] ) -> t.Iterator[t.AnyStr]: """Request contexts disappear when the response is started on the server. This is done for efficiency reasons and to make it less likely to encounter diff --git a/src/flask/json/provider.py b/src/flask/json/provider.py index 3c22bc8fc2..46d64d6a34 100644 --- a/src/flask/json/provider.py +++ b/src/flask/json/provider.py @@ -134,9 +134,7 @@ class DefaultJSONProvider(JSONProvider): method) will call the ``__html__`` method to get a string. """ - default: t.Callable[[t.Any], t.Any] = staticmethod( - _default - ) # type: ignore[assignment] + default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment] """Apply this function to any object that :meth:`json.dumps` does not know how to serialize. It should return a valid JSON type or raise a ``TypeError``. diff --git a/src/flask/sansio/app.py b/src/flask/sansio/app.py index 0f7d2cbf14..fdc4714e4c 100644 --- a/src/flask/sansio/app.py +++ b/src/flask/sansio/app.py @@ -35,9 +35,10 @@ if t.TYPE_CHECKING: # pragma: no cover from werkzeug.wrappers import Response as BaseResponse - from .blueprints import Blueprint + from ..testing import FlaskClient from ..testing import FlaskCliRunner + from .blueprints import Blueprint T_shell_context_processor = t.TypeVar( "T_shell_context_processor", bound=ft.ShellContextProcessorCallable @@ -905,7 +906,9 @@ def redirect(self, location: str, code: int = 302) -> BaseResponse: Moved from ``flask.redirect``, which calls this method. """ return _wz_redirect( - location, code=code, Response=self.response_class # type: ignore[arg-type] + location, + code=code, + Response=self.response_class, # type: ignore[arg-type] ) def inject_url_defaults(self, endpoint: str, values: dict) -> None: diff --git a/src/flask/sessions.py b/src/flask/sessions.py index e5650d6862..34f71d8c05 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -14,7 +14,8 @@ if t.TYPE_CHECKING: # pragma: no cover from .app import Flask - from .wrappers import Request, Response + from .wrappers import Request + from .wrappers import Response class SessionMixin(MutableMapping): diff --git a/src/flask/views.py b/src/flask/views.py index c7a2b621c8..bfc18af3b8 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -6,7 +6,6 @@ from .globals import current_app from .globals import request - http_method_funcs = frozenset( ["get", "post", "head", "options", "delete", "put", "trace", "patch"] ) diff --git a/tests/test_appctx.py b/tests/test_appctx.py index aa3a8b4e5c..ca9e079e65 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -196,17 +196,14 @@ def test_clean_pop(app): @app.teardown_request def teardown_req(error=None): - 1 / 0 + raise ZeroDivisionError @app.teardown_appcontext def teardown_app(error=None): called.append("TEARDOWN") - try: - with app.test_request_context(): - called.append(flask.current_app.name) - except ZeroDivisionError: - pass + with app.app_context(): + called.append(flask.current_app.name) assert called == ["flask_test", "TEARDOWN"] assert not flask.current_app diff --git a/tests/test_apps/subdomaintestmodule/__init__.py b/tests/test_apps/subdomaintestmodule/__init__.py index 9b83c0d734..b4ce4b1683 100644 --- a/tests/test_apps/subdomaintestmodule/__init__.py +++ b/tests/test_apps/subdomaintestmodule/__init__.py @@ -1,4 +1,3 @@ from flask import Module - mod = Module(__name__, "foo", subdomain="foo") diff --git a/tests/test_basic.py b/tests/test_basic.py index d8bca98057..214cfee078 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -19,7 +19,6 @@ import flask - require_cpython_gc = pytest.mark.skipif( python_implementation() != "CPython", reason="Requires CPython GC behavior", @@ -190,7 +189,8 @@ def options(): def test_werkzeug_routing(app, client): - from werkzeug.routing import Submount, Rule + from werkzeug.routing import Rule + from werkzeug.routing import Submount app.url_map.add( Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) @@ -210,7 +210,8 @@ def index(): def test_endpoint_decorator(app, client): - from werkzeug.routing import Submount, Rule + from werkzeug.routing import Rule + from werkzeug.routing import Submount app.url_map.add( Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) @@ -431,9 +432,9 @@ def dump_session_contents(): client.get("/") s = flask.session assert s["t"] == (1, 2, 3) - assert type(s["b"]) is bytes + assert type(s["b"]) is bytes # noqa: E721 assert s["b"] == b"\xff" - assert type(s["m"]) is Markup + assert type(s["m"]) is Markup # noqa: E721 assert s["m"] == Markup("") assert s["u"] == the_uuid assert s["d"] == now @@ -784,7 +785,7 @@ def teardown_request2(exc): @app.route("/") def fails(): - 1 // 0 + raise ZeroDivisionError rv = client.get("/") assert rv.status_code == 500 @@ -851,7 +852,7 @@ def index(): @app.route("/error") def error(): - 1 // 0 + raise ZeroDivisionError @app.route("/forbidden") def error2(): @@ -877,7 +878,7 @@ def internal_server_error(e): @app.route("/") def broken_func(): - 1 // 0 + raise ZeroDivisionError @app.after_request def after_request(resp): @@ -1047,12 +1048,13 @@ def test_error_handler_after_processor_error(app, client): @app.before_request def before_request(): if _trigger == "before": - 1 // 0 + raise ZeroDivisionError @app.after_request def after_request(response): if _trigger == "after": - 1 // 0 + raise ZeroDivisionError + return response @app.route("/") @@ -1507,7 +1509,7 @@ def test_exception_propagation(app, client, key): @app.route("/") def index(): - 1 // 0 + raise ZeroDivisionError if key is not None: app.config[key] = True diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 76cee66003..69bc71ad8f 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -634,10 +634,11 @@ def index(): def test_context_processing(app, client): answer_bp = flask.Blueprint("answer_bp", __name__) - template_string = lambda: flask.render_template_string( # noqa: E731 - "{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}" - "{% if answer %}{{ answer }} is the answer.{% endif %}" - ) + def template_string(): + return flask.render_template_string( + "{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}" + "{% if answer %}{{ answer }} is the answer.{% endif %}" + ) # App global context processor @answer_bp.app_context_processor diff --git a/tests/test_signals.py b/tests/test_signals.py index 6174fe836d..32ab333e63 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -98,7 +98,7 @@ def test_request_exception_signal(): @app.route("/") def index(): - 1 // 0 + raise ZeroDivisionError def record(sender, exception): recorded.append(exception) @@ -169,7 +169,7 @@ def record_teardown(sender, exc): @app.route("/") def index(): - 1 // 0 + raise ZeroDivisionError flask.appcontext_tearing_down.connect(record_teardown, app) try: diff --git a/tests/test_testing.py b/tests/test_testing.py index 6a90cc4504..de0521520a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -219,7 +219,7 @@ def index(): @app.route("/other") def other(): - 1 // 0 + raise ZeroDivisionError with client: resp = client.get("/") @@ -227,18 +227,15 @@ def other(): assert resp.data == b"Hello World!" assert resp.status_code == 200 + with client: resp = client.get("/other") assert not hasattr(flask.g, "value") assert b"Internal Server Error" in resp.data assert resp.status_code == 500 flask.g.value = 23 - try: - flask.g.value - except (AttributeError, RuntimeError): - pass - else: - raise AssertionError("some kind of exception expected") + with pytest.raises(RuntimeError): + flask.g.value # noqa: B018 def test_reuse_client(client): diff --git a/tests/test_views.py b/tests/test_views.py index 8d870def21..eab5eda286 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -41,10 +41,10 @@ def post(self): def test_view_patching(app): class Index(flask.views.MethodView): def get(self): - 1 // 0 + raise ZeroDivisionError def post(self): - 1 // 0 + raise ZeroDivisionError class Other(Index): def get(self): diff --git a/tests/typing/typing_app_decorators.py b/tests/typing/typing_app_decorators.py index 6b2188aa60..c8d011136c 100644 --- a/tests/typing/typing_app_decorators.py +++ b/tests/typing/typing_app_decorators.py @@ -1,7 +1,5 @@ from __future__ import annotations -import typing as t - from flask import Flask from flask import Response @@ -29,10 +27,10 @@ async def before_async() -> None: @app.teardown_appcontext -def teardown_sync(exc: t.Optional[BaseException]) -> None: +def teardown_sync(exc: BaseException | None) -> None: ... @app.teardown_appcontext -async def teardown_async(exc: t.Optional[BaseException]) -> None: +async def teardown_async(exc: BaseException | None) -> None: ... diff --git a/tests/typing/typing_route.py b/tests/typing/typing_route.py index bbd044ae12..8bc271b21d 100644 --- a/tests/typing/typing_route.py +++ b/tests/typing/typing_route.py @@ -29,12 +29,12 @@ def hello_json() -> Response: @app.route("/json/dict") -def hello_json_dict() -> t.Dict[str, t.Any]: +def hello_json_dict() -> dict[str, t.Any]: return {"response": "Hello, World!"} @app.route("/json/dict") -def hello_json_list() -> t.List[t.Any]: +def hello_json_list() -> list[t.Any]: return [{"message": "Hello"}, {"message": "World"}]