From e619c39ab8a9ac6186eee866ad964f01c43bf6bf Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Wed, 17 Apr 2024 19:19:18 +0200 Subject: [PATCH] chore: migrate to `ruff` code formatter (#2230) * chore: migrate to `ruff` code formatter * chore: update falcon/inspect.py per @CaselIT's suggestion Co-authored-by: Federico Caselli * chore: fix spelling * chore(ruff): use parentheses to prevent too sparse layout --------- Co-authored-by: Federico Caselli --- .github/workflows/tests.yaml | 4 ++-- CONTRIBUTING.md | 8 ++++---- README.rst | 5 +---- examples/look/look/images.py | 1 - examples/things_advanced.py | 1 - examples/things_advanced_asgi.py | 1 - falcon/asgi/request.py | 1 - falcon/bench/nuts/nuts/app.py | 1 - falcon/cmd/inspect_app.py | 2 +- falcon/inspect.py | 6 ++++-- falcon/media/multipart.py | 2 -- falcon/middleware.py | 1 - falcon/request_helpers.py | 2 +- falcon/routing/compiled.py | 3 +-- falcon/routing/static.py | 1 - falcon/testing/client.py | 2 -- falcon/testing/helpers.py | 3 --- falcon/util/deprecation.py | 1 - falcon/util/misc.py | 1 + falcon/util/reader.py | 2 +- falcon/util/structures.py | 11 ++++------- falcon/util/sync.py | 2 +- falcon/util/uri.py | 3 +-- pyproject.toml | 9 +++++++++ setup.py | 1 - tests/asgi/test_boundedstream_asgi.py | 8 ++++---- tests/asgi/test_example_asgi.py | 1 - tests/asgi/test_request_context_asgi.py | 2 -- tests/asgi/test_response_media_asgi.py | 5 ++--- tests/asgi/test_sse.py | 5 ++--- tests/asgi/test_ws.py | 19 +++++++++---------- tests/test_after_hooks.py | 3 --- tests/test_before_hooks.py | 1 - tests/test_custom_router.py | 1 - tests/test_example.py | 1 - tests/test_headers.py | 11 +++++------ tests/test_hello.py | 2 -- tests/test_inspect.py | 2 +- tests/test_media_multipart.py | 8 ++++---- tests/test_middleware.py | 1 - tests/test_request_access_route.py | 1 - tests/test_request_attrs.py | 1 - tests/test_request_context.py | 2 -- tests/test_request_forwarded.py | 1 - tests/test_response_media.py | 4 ++-- tests/test_utils.py | 6 +++--- tox.ini | 14 ++++++++------ 47 files changed, 71 insertions(+), 102 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6098b43d7..30556b9d2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,9 +23,9 @@ jobs: - "ubuntu-latest" toxenv: - "pep8" - - "blue" - - "pep8-examples" - "pep8-docstrings" + - "pep8-examples" + - "ruff" - "mypy" - "mypy_tests" - "py312" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b28054167..f5dbd5cbc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,13 +28,13 @@ Please note that all contributors and maintainers of this project are subject to Before submitting a pull request, please ensure you have added or updated tests as appropriate, and that all existing tests still pass with your changes. -Please also ensure that your coding style follows PEP 8 and the ``blue`` formatting style. +Please also ensure that your coding style follows PEP 8 and the ``ruff`` formatting style. -In order to reformat your code with ``blue``, simply issue: +In order to reformat your code with ``ruff``, simply issue: ```bash -$ pip install -U blue -$ blue . +$ pip install -U ruff +$ ruff format ``` You can check all this by running ``tox`` from within the Falcon project directory. Your environment must be based on CPython 3.8, 3.10, 3.11 or 3.12: diff --git a/README.rst b/README.rst index 738b2f2b8..82c93d832 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ > -|Build Status| |Docs| |codecov.io| |Blue| +|Build Status| |Docs| |codecov.io| The Falcon Web Framework ======================== @@ -1049,6 +1049,3 @@ limitations under the License. :target: https://github.com/falconry/falcon/actions?query=workflow%3A%22Run+tests%22 .. |codecov.io| image:: https://codecov.io/gh/falconry/falcon/branch/master/graphs/badge.svg :target: http://codecov.io/gh/falconry/falcon -.. |Blue| image:: https://img.shields.io/badge/code%20style-blue-blue.svg - :target: https://blue.readthedocs.io/ - :alt: code style: blue diff --git a/examples/look/look/images.py b/examples/look/look/images.py index e355a68a1..3b4e84a0c 100644 --- a/examples/look/look/images.py +++ b/examples/look/look/images.py @@ -31,7 +31,6 @@ def on_post(self, req, resp): class ImageStore: - _CHUNK_SIZE_BYTES = 4096 # Note the use of dependency injection for standard library diff --git a/examples/things_advanced.py b/examples/things_advanced.py index 7e68173f8..5c28280a7 100644 --- a/examples/things_advanced.py +++ b/examples/things_advanced.py @@ -26,7 +26,6 @@ def handle(ex, req, resp, params): class SinkAdapter: - engines = { 'ddg': 'https://duckduckgo.com', 'y': 'https://search.yahoo.com/search', diff --git a/examples/things_advanced_asgi.py b/examples/things_advanced_asgi.py index 3f958983b..dcd816ef5 100644 --- a/examples/things_advanced_asgi.py +++ b/examples/things_advanced_asgi.py @@ -26,7 +26,6 @@ async def handle(ex, req, resp, params): class SinkAdapter: - engines = { 'ddg': 'https://duckduckgo.com', 'y': 'https://search.yahoo.com/search', diff --git a/falcon/asgi/request.py b/falcon/asgi/request.py index 3bb5f5c86..9650bfb63 100644 --- a/falcon/asgi/request.py +++ b/falcon/asgi/request.py @@ -384,7 +384,6 @@ class Request(request.Request): _wsgi_errors = None def __init__(self, scope, receive, first_event=None, options=None): - # ===================================================================== # Prepare headers # ===================================================================== diff --git a/falcon/bench/nuts/nuts/app.py b/falcon/bench/nuts/nuts/app.py index 37ca87029..d7c9cb1e7 100644 --- a/falcon/bench/nuts/nuts/app.py +++ b/falcon/bench/nuts/nuts/app.py @@ -8,7 +8,6 @@ def create(): def setup_app(config): - return make_app( config.app.root, static_root=config.app.static_root, diff --git a/falcon/cmd/inspect_app.py b/falcon/cmd/inspect_app.py index 260ef8dc4..bd3844f88 100644 --- a/falcon/cmd/inspect_app.py +++ b/falcon/cmd/inspect_app.py @@ -15,6 +15,7 @@ """ Script that prints out the routes of an App instance. """ + import argparse import importlib import os @@ -59,7 +60,6 @@ def make_parser(): def load_app(parser, args): - try: module, instance = args.app_module.split(':', 1) except ValueError: diff --git a/falcon/inspect.py b/falcon/inspect.py index 62fc74c28..e5bc4c690 100644 --- a/falcon/inspect.py +++ b/falcon/inspect.py @@ -13,6 +13,7 @@ # limitations under the License. """Inspect utilities for falcon applications.""" + from functools import partial import inspect from typing import Callable # NOQA: F401 @@ -89,8 +90,9 @@ def inspect_my_router(router): def wraps(fn): if router_class in _supported_routers: raise ValueError( - 'Another function is already registered' - ' for the router {}'.format(router_class) + 'Another function is already registered for the router {}'.format( + router_class + ) ) _supported_routers[router_class] = fn return fn diff --git a/falcon/media/multipart.py b/falcon/media/multipart.py index 5b55d4b4f..240a4781c 100644 --- a/falcon/media/multipart.py +++ b/falcon/media/multipart.py @@ -272,7 +272,6 @@ def content_type(self): @property def filename(self): if self._filename is None: - if self._content_disposition is None: value = self._headers.get(b'content-disposition', b'') self._content_disposition = parse_header(value.decode()) @@ -308,7 +307,6 @@ def secure_filename(self): @property def name(self): if self._name is None: - if self._content_disposition is None: value = self._headers.get(b'content-disposition', b'') self._content_disposition = parse_header(value.decode()) diff --git a/falcon/middleware.py b/falcon/middleware.py index e6e8c00be..195892a29 100644 --- a/falcon/middleware.py +++ b/falcon/middleware.py @@ -108,7 +108,6 @@ def process_response(self, req: Request, resp: Response, resource, req_succeeded and req.method == 'OPTIONS' and req.get_header('Access-Control-Request-Method') ): - # NOTE(kgriffs): This is a CORS preflight request. Patch the # response accordingly. diff --git a/falcon/request_helpers.py b/falcon/request_helpers.py index 7534db108..f3cbf51cb 100644 --- a/falcon/request_helpers.py +++ b/falcon/request_helpers.py @@ -30,7 +30,7 @@ # (see also: https://www.python.org/dev/peps/pep-3333/#unicode-issues) # _COOKIE_NAME_RESERVED_CHARS = re.compile( - '[\x00-\x1F\x7F-\xFF()<>@,;:\\\\"/[\\]?={} \x09]' + '[\x00-\x1f\x7f-\xff()<>@,;:\\\\"/[\\]?={} \x09]' ) # NOTE(kgriffs): strictly speaking, the weakness indicator is diff --git a/falcon/routing/compiled.py b/falcon/routing/compiled.py index 0b45edbc1..b7d6c3244 100644 --- a/falcon/routing/compiled.py +++ b/falcon/routing/compiled.py @@ -651,8 +651,7 @@ def _compile(self): src_lines.append( # PERF(kgriffs): Explicit return of None is faster than implicit - _TAB_STR - + 'return None' + _TAB_STR + 'return None' ) self._finder_src = '\n'.join(src_lines) diff --git a/falcon/routing/static.py b/falcon/routing/static.py index ff9bbef2a..4e7c27706 100644 --- a/falcon/routing/static.py +++ b/falcon/routing/static.py @@ -173,7 +173,6 @@ def __call__(self, req, resp): or '//' in without_prefix or len(without_prefix) > self._MAX_NON_PREFIXED_LEN ): - raise falcon.HTTPNotFound() normalized = os.path.normpath(without_prefix) diff --git a/falcon/testing/client.py b/falcon/testing/client.py index fce0f4d0a..05a59f599 100644 --- a/falcon/testing/client.py +++ b/falcon/testing/client.py @@ -452,7 +452,6 @@ def simulate_request( asgi_chunk_size=4096, asgi_disconnect_ttl=300, ) -> _ResultBase: - """Simulate a request to a WSGI or ASGI application. Performs a request against a WSGI or ASGI application. In the case of @@ -671,7 +670,6 @@ async def _simulate_request_asgi( _one_shot=True, _stream_result=False, ) -> _ResultBase: - """Simulate a request to an ASGI application. Keyword Args: diff --git a/falcon/testing/helpers.py b/falcon/testing/helpers.py index 39e8c12f8..d0d92dbff 100644 --- a/falcon/testing/helpers.py +++ b/falcon/testing/helpers.py @@ -864,7 +864,6 @@ def create_scope( include_server=True, cookies=None, ) -> Dict[str, Any]: - """Create a mock ASGI scope ``dict`` for simulating HTTP requests. Keyword Args: @@ -1002,7 +1001,6 @@ def create_scope_ws( subprotocols=None, spec_version='2.1', ) -> Dict[str, Any]: - """Create a mock ASGI scope ``dict`` for simulating WebSocket requests. Keyword Args: @@ -1089,7 +1087,6 @@ def create_environ( root_path=None, cookies=None, ) -> Dict[str, Any]: - """Create a mock PEP-3333 environ ``dict`` for simulating WSGI requests. Keyword Args: diff --git a/falcon/util/deprecation.py b/falcon/util/deprecation.py index 5e2607ad7..ed1229916 100644 --- a/falcon/util/deprecation.py +++ b/falcon/util/deprecation.py @@ -66,7 +66,6 @@ def deprecated( """ def decorator(func: Callable[..., Any]) -> Callable[[Callable[..., Any]], Any]: - object_name = 'property' if is_property else 'function' post_name = '' if is_property else '(...)' message = 'Call to deprecated {} {}{}. {}'.format( diff --git a/falcon/util/misc.py b/falcon/util/misc.py index 3690aeca4..bbd3080ed 100644 --- a/falcon/util/misc.py +++ b/falcon/util/misc.py @@ -22,6 +22,7 @@ now = falcon.http_now() """ + import datetime import functools import http diff --git a/falcon/util/reader.py b/falcon/util/reader.py index 263d66716..b97484f7f 100644 --- a/falcon/util/reader.py +++ b/falcon/util/reader.py @@ -13,6 +13,7 @@ # limitations under the License. """Buffered stream reader.""" + from __future__ import annotations import functools @@ -189,7 +190,6 @@ def _finalize_read_until( next_chunk: Optional[bytes] = None, next_chunk_len: int = 0, ) -> bytes: - if delimiter_pos < 0 and delimiter is not None: delimiter_pos = self._buffer.find(delimiter, self._buffer_pos) diff --git a/falcon/util/structures.py b/falcon/util/structures.py index 5a9e51176..f5b4e97c5 100644 --- a/falcon/util/structures.py +++ b/falcon/util/structures.py @@ -25,6 +25,7 @@ things = falcon.CaseInsensitiveDict() """ + from __future__ import annotations from collections.abc import Mapping @@ -146,14 +147,11 @@ class Context: # merely to let mypy know this is a namespace object. if TYPE_CHECKING: - def __getattr__(self, name: str) -> Any: - ... + def __getattr__(self, name: str) -> Any: ... - def __setattr__(self, name: str, value: Any) -> None: - ... + def __setattr__(self, name: str, value: Any) -> None: ... - def __delattr__(self, name: str) -> None: - ... + def __delattr__(self, name: str) -> None: ... def __contains__(self, key: str) -> bool: return self.__dict__.__contains__(key) @@ -217,7 +215,6 @@ def pop(self, key: str, default: Optional[Any] = None) -> Optional[Any]: return self.__dict__.pop(key, default) def popitem(self) -> Tuple[str, Any]: - return self.__dict__.popitem() def setdefault( diff --git a/falcon/util/sync.py b/falcon/util/sync.py index 96d05c058..f19d9f1ae 100644 --- a/falcon/util/sync.py +++ b/falcon/util/sync.py @@ -198,7 +198,7 @@ def _should_wrap_non_coroutines() -> bool: def _wrap_non_coroutine_unsafe( - func: Optional[Callable[..., Any]] + func: Optional[Callable[..., Any]], ) -> Union[Callable[..., Awaitable[Any]], Callable[..., Any], None]: """Wrap a coroutine using ``wrap_sync_to_async_unsafe()`` for internal test cases. diff --git a/falcon/util/uri.py b/falcon/util/uri.py index 5daa7c68a..15bef3c97 100644 --- a/falcon/util/uri.py +++ b/falcon/util/uri.py @@ -22,6 +22,7 @@ name, port = uri.parse_host('example.org:8080') """ + from typing import Callable from typing import Dict from typing import List @@ -57,7 +58,6 @@ def _create_char_encoder(allowed_chars: str) -> Callable[[int], str]: - lookup = {} for code_point in range(256): @@ -74,7 +74,6 @@ def _create_char_encoder(allowed_chars: str) -> Callable[[int], str]: def _create_str_encoder( is_value: bool, check_is_escaped: bool = False ) -> Callable[[str], str]: - allowed_chars = _UNRESERVED if is_value else _ALL_ALLOWED allowed_chars_plus_percent = allowed_chars + '%' encode_char = _create_char_encoder(allowed_chars) diff --git a/pyproject.toml b/pyproject.toml index 44a829feb..73fbcea1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,10 +82,19 @@ extend-exclude = "falcon/vendor" [tool.blue] + # NOTE(vytas): Before switching to Ruff, Falcon used the Blue formatter. + # With the below settings, accidentally running blue should yield + # only minor cosmetic changes in a handful of files. target-version = ["py37"] line-length = 88 extend-exclude = "falcon/vendor" +[tool.ruff] + target-version = "py37" + format.quote-style = "single" + line-length = 88 + extend-exclude = ["falcon/vendor"] + [tool.pytest.ini_options] filterwarnings = [ "ignore:Unknown REQUEST_METHOD. '(CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT|TRACE|CHECKIN|CHECKOUT|COPY|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|REPORT|UNCHECKIN|UNLOCK|UPDATE|VERSION-CONTROL)':wsgiref.validate.WSGIWarning", diff --git a/setup.py b/setup.py index c3db954af..edae6c702 100644 --- a/setup.py +++ b/setup.py @@ -139,7 +139,6 @@ def load_description(): # NOTE(kgriffs): PyPI does not support the raw directive for readme_line in io.open('README.rst', 'r', encoding='utf-8'): - # NOTE(vytas): The patron list largely builds upon raw sections if readme_line.startswith('.. Patron list starts'): in_patron_list = True diff --git a/tests/asgi/test_boundedstream_asgi.py b/tests/asgi/test_boundedstream_asgi.py index 3d80c7f86..135d7441d 100644 --- a/tests/asgi/test_boundedstream_asgi.py +++ b/tests/asgi/test_boundedstream_asgi.py @@ -11,9 +11,9 @@ [ b'', b'\x00', - b'\x00\xFF', + b'\x00\xff', b'catsup', - b'\xDE\xAD\xBE\xEF' * 512, + b'\xde\xad\xbe\xef' * 512, testing.rand_string(1, 2048), os.urandom(100 * 2**20), ], @@ -193,9 +193,9 @@ async def receive(): [ b'', b'\x00', - b'\x00\xFF', + b'\x00\xff', b'catsup', - b'\xDE\xAD\xBE\xEF' * 512, + b'\xde\xad\xbe\xef' * 512, testing.rand_string(1, 2048).encode(), ], ids=['empty', 'null', 'null-ff', 'normal', 'long', 'random'], diff --git a/tests/asgi/test_example_asgi.py b/tests/asgi/test_example_asgi.py index cee1138a1..f67ee3af6 100644 --- a/tests/asgi/test_example_asgi.py +++ b/tests/asgi/test_example_asgi.py @@ -27,7 +27,6 @@ async def handle(ex, req, resp, params): class SinkAdapter: - engines = { 'ddg': 'https://duckduckgo.com', 'y': 'https://search.yahoo.com/search', diff --git a/tests/asgi/test_request_context_asgi.py b/tests/asgi/test_request_context_asgi.py index e06b965d5..31d1f965b 100644 --- a/tests/asgi/test_request_context_asgi.py +++ b/tests/asgi/test_request_context_asgi.py @@ -20,7 +20,6 @@ def test_default_request_context( assert req.context.get('note') == req.context['note'] def test_custom_request_context(self): - # Define a Request-alike with a custom context type class MyCustomContextType: pass @@ -32,7 +31,6 @@ class MyCustomRequest(Request): assert isinstance(req.context, MyCustomContextType) def test_custom_request_context_failure(self): - # Define a Request-alike with a non-callable custom context type class MyCustomRequest(Request): context_type = False diff --git a/tests/asgi/test_response_media_asgi.py b/tests/asgi/test_response_media_asgi.py index 3d5856282..a55c6e606 100644 --- a/tests/asgi/test_response_media_asgi.py +++ b/tests/asgi/test_response_media_asgi.py @@ -59,7 +59,7 @@ async def on_get(self, req, resp): '', 'I am a \u1d0a\ua731\u1d0f\u0274 string.', ['\u2665', '\u2660', '\u2666', '\u2663'], - {'message': '\xa1Hello Unicode! \U0001F638'}, + {'message': '\xa1Hello Unicode! \U0001f638'}, { 'description': 'A collection of primitive Python type examples.', 'bool': False is not True and True is not False, @@ -69,7 +69,7 @@ async def on_get(self, req, resp): 'list': ['a', 'sequence', 'of', 'items'], 'none': None, 'str': 'ASCII string', - 'unicode': 'Hello Unicode! \U0001F638', + 'unicode': 'Hello Unicode! \U0001f638', }, ], ) @@ -221,7 +221,6 @@ def run_test(test_fn): class TestResource: async def on_get(self, req, resp): - await test_fn(resp) resp.text = None diff --git a/tests/asgi/test_sse.py b/tests/asgi/test_sse.py index eac00b4b9..df04688c7 100644 --- a/tests/asgi/test_sse.py +++ b/tests/asgi/test_sse.py @@ -83,7 +83,7 @@ def test_multiple_events(): '\n' ': Serve with chips.\n' 'retry: 100\n' - 'data: guacamole \u1F951\n' + 'data: guacamole \u1f951\n' '\n' 'retry: 100\n' 'data: {"condiment": "salsa"}\n' @@ -101,7 +101,7 @@ async def emitter(): data=b'onions', event='topping', event_id='5678', retry=100 ), SSEvent( - text='guacamole \u1F951', retry=100, comment='Serve with chips.' + text='guacamole \u1f951', retry=100, comment='Serve with chips.' ), SSEvent(json={'condiment': 'salsa'}, retry=100), ]: @@ -173,7 +173,6 @@ async def _test(): async with testing.ASGIConductor(app) as conductor: # NOTE(vytas): Using the get_stream() alias. async with conductor.get_stream() as sr: - event_count = 0 result_text = '' diff --git a/tests/asgi/test_ws.py b/tests/asgi/test_ws.py index f45ea9758..d00f71dc6 100644 --- a/tests/asgi/test_ws.py +++ b/tests/asgi/test_ws.py @@ -442,7 +442,7 @@ async def on_websocket(self, req, ws): for __ in range(3): try: await ws.receive_media() - except (ValueError): + except ValueError: self.deserialize_error_count += 1 finally: self.finished.set() @@ -467,9 +467,9 @@ def serialize(self, media: object) -> str: def deserialize(self, payload: str) -> object: return rapidjson.loads(payload) - app.ws_options.media_handlers[ - falcon.WebSocketPayloadType.TEXT - ] = RapidJSONHandler() + app.ws_options.media_handlers[falcon.WebSocketPayloadType.TEXT] = ( + RapidJSONHandler() + ) if custom_data: @@ -481,9 +481,9 @@ def serialize(self, media: object) -> bytes: def deserialize(self, payload: bytes) -> object: return cbor2.loads(payload) - app.ws_options.media_handlers[ - falcon.WebSocketPayloadType.BINARY - ] = CBORHandler() + app.ws_options.media_handlers[falcon.WebSocketPayloadType.BINARY] = ( + CBORHandler() + ) async with conductor as c: async with c.simulate_ws() as ws: @@ -503,7 +503,7 @@ def deserialize(self, payload: bytes) -> object: # ensure we aren't getting any false-positives. await ws.send_text('"DEADBEEF"') await ws.send_text('DEADBEEF') - await ws.send_data(b'\xDE\xAD\xBE\xEF') + await ws.send_data(b'\xde\xad\xbe\xef') await resource.finished.wait() @@ -1119,7 +1119,6 @@ async def on_websocket(self, req, ws): @pytest.mark.skipif(msgpack, reason='test requires msgpack lib to be missing') def test_msgpack_missing(): - options = WebSocketOptions() handler = options.media_handlers[falcon.WebSocketPayloadType.BINARY] @@ -1231,7 +1230,7 @@ async def process_resource_ws(self, req, ws, res, params): if handler_has_ws: - async def handle_foobar(req, resp, ex, param, ws=None): # type: ignore + async def handle_foobar(req, resp, ex, param, ws=None): # type: ignore raise thing(status) else: diff --git a/tests/test_after_hooks.py b/tests/test_after_hooks.py index 4f95914b7..4f5cc1f00 100644 --- a/tests/test_after_hooks.py +++ b/tests/test_after_hooks.py @@ -147,7 +147,6 @@ async def on_post(self, req, resp): @falcon.after(cuteness, 'fluffy', postfix=' and innocent') @falcon.after(fluffiness, 'kitten') class WrappedClassResource: - # Test that the decorator skips non-callables on_post = False @@ -196,7 +195,6 @@ def on_get(self, req, resp, field1, field2): # at once for the sake of simplicity @falcon.after(resource_aware_cuteness) class ClassResourceWithAwareHooks: - # Test that the decorator skips non-callables on_delete = False @@ -342,7 +340,6 @@ def test_wrapped_resource_with_hooks_aware_of_resource(client, wrapped_resource_ class ResourceAwareGameHook: - VALUES = ('rock', 'scissors', 'paper') @classmethod diff --git a/tests/test_before_hooks.py b/tests/test_before_hooks.py index 882a5964c..ebc936030 100644 --- a/tests/test_before_hooks.py +++ b/tests/test_before_hooks.py @@ -130,7 +130,6 @@ def on_get(self, req, resp, doc=None): @falcon.before(bunnies) class WrappedClassResource: - _some_fish = Fish() # Test non-callable should be skipped by decorator diff --git a/tests/test_custom_router.py b/tests/test_custom_router.py index 3640c8404..b21b0c447 100644 --- a/tests/test_custom_router.py +++ b/tests/test_custom_router.py @@ -79,7 +79,6 @@ def find(self, uri, req=None): @pytest.mark.parametrize('asgi', [True, False]) def test_can_pass_additional_params_to_add_route(asgi): - check = [] class CustomRouter: diff --git a/tests/test_example.py b/tests/test_example.py index bb430a94c..400458451 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -25,7 +25,6 @@ def handle(req, resp, ex, params): class SinkAdapter: - engines = { 'ddg': 'https://duckduckgo.com', 'y': 'https://search.yahoo.com/search', diff --git a/tests/test_headers.py b/tests/test_headers.py index bf9d47536..afdf18e0c 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -119,7 +119,6 @@ def on_put(self, req, resp): class LocationHeaderUnicodeResource: - URL1 = '/\u00e7runchy/bacon' URL2 = 'ab\u00e7' @@ -156,14 +155,14 @@ def on_patch(self, req, resp): def on_post(self, req, resp): resp.set_headers( [ - ('X-symb\u00F6l', 'thing'), + ('X-symb\u00f6l', 'thing'), ] ) def on_put(self, req, resp): resp.set_headers( [ - ('X-Thing', '\u00FF'), + ('X-Thing', '\u00ff'), ] ) @@ -498,7 +497,7 @@ def test_default_media_type(self, client): @pytest.mark.parametrize( 'content_type,body', [ - ('text/plain; charset=UTF-8', 'Hello Unicode! \U0001F638'), + ('text/plain; charset=UTF-8', 'Hello Unicode! \U0001f638'), # NOTE(kgriffs): This only works because the client defaults to # ISO-8859-1 IFF the media type is 'text'. ('text/plain', 'Hello ISO-8859-1!'), @@ -514,7 +513,7 @@ def test_override_default_media_type(self, asgi, client, content_type, body): @pytest.mark.parametrize('asgi', [True, False]) def test_override_default_media_type_missing_encoding(self, asgi, client): - body = '{"msg": "Hello Unicode! \U0001F638"}' + body = '{"msg": "Hello Unicode! \U0001f638"}' client.app = create_app(asgi=asgi, media_type='application/json') client.app.add_route('/', testing.SimpleTestResource(body=body)) @@ -523,7 +522,7 @@ def test_override_default_media_type_missing_encoding(self, asgi, client): assert result.content == body.encode('utf-8') assert isinstance(result.text, str) assert result.text == body - assert result.json == {'msg': 'Hello Unicode! \U0001F638'} + assert result.json == {'msg': 'Hello Unicode! \U0001f638'} def test_response_header_helpers_on_get(self, client): last_modified = datetime(2013, 1, 1, 10, 30, 30) diff --git a/tests/test_hello.py b/tests/test_hello.py index e444e529e..1bef8d773 100644 --- a/tests/test_hello.py +++ b/tests/test_hello.py @@ -71,7 +71,6 @@ def on_head(self, req, resp): class ClosingBytesIO(io.BytesIO): - close_called = False def close(self): @@ -80,7 +79,6 @@ def close(self): class NonClosingBytesIO(io.BytesIO): - # Not callable; test that CloseableStreamIterator ignores it close = False # type: ignore diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 4d11dbb82..84b9c218b 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -233,7 +233,7 @@ def test_middleware_tree(self, asgi): mi = inspect.inspect_middleware(make_app_async() if asgi else make_app()) def test(tl, names, cls): - for (t, n, c) in zip(tl, names, cls): + for t, n, c in zip(tl, names, cls): assert isinstance(t, inspect.MiddlewareTreeItemInfo) assert t.name == n assert t.class_name == c diff --git a/tests/test_media_multipart.py b/tests/test_media_multipart.py index 7edf51f4c..6643a3146 100644 --- a/tests/test_media_multipart.py +++ b/tests/test_media_multipart.py @@ -60,7 +60,7 @@ b'--BOUNDARY\r\n' b'Content-Disposition: form-data; name="file"; filename="bytes"\r\n' b'Content-Type: application/x-falcon\r\n\r\n' - + b'123456789abcdef\n' * 64 * 1024 * 2 + + (b'123456789abcdef\n' * 64 * 1024 * 2) + b'\r\n' b'--BOUNDARY\r\n' b'Content-Disposition: form-data; name="empty"\r\n' @@ -622,9 +622,9 @@ def on_post(self, req, resp): resp.media = example parser = media.MultipartFormHandler() - parser.parse_options.media_handlers[ - 'multipart/mixed' - ] = media.MultipartFormHandler() + parser.parse_options.media_handlers['multipart/mixed'] = ( + media.MultipartFormHandler() + ) app = falcon.App() app.req_options.media_handlers[falcon.MEDIA_MULTIPART] = parser diff --git a/tests/test_middleware.py b/tests/test_middleware.py index b6b1b9d30..04de946e8 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -122,7 +122,6 @@ def process_request(self, req, resp): class ResponseCacheMiddlware: - PROCESS_REQUEST_CACHED_BODY = {'cached': True} PROCESS_RESOURCE_CACHED_BODY = {'cached': True, 'resource': True} diff --git a/tests/test_request_access_route.py b/tests/test_request_access_route.py index 202dac223..7571efae7 100644 --- a/tests/test_request_access_route.py +++ b/tests/test_request_access_route.py @@ -83,7 +83,6 @@ def test_malformed_rfc_forwarded(asgi): @pytest.mark.parametrize('include_localhost', [True, False]) def test_x_forwarded_for(asgi, include_localhost): - forwarded_for = '192.0.2.43, 2001:db8:cafe::17,unknown, _hidden, 203.0.113.60' if include_localhost: diff --git a/tests/test_request_attrs.py b/tests/test_request_attrs.py index 466a0c875..8ae5b72dc 100644 --- a/tests/test_request_attrs.py +++ b/tests/test_request_attrs.py @@ -709,7 +709,6 @@ def test_date(self, asgi, header, attr): ], ) def test_date_invalid(self, asgi, header, attr): - # Date formats don't conform to RFC 1123 headers = {header: 'Thu, 04 Apr 2013'} expected_desc = ( diff --git a/tests/test_request_context.py b/tests/test_request_context.py index 90bf9b90b..6d26a27a2 100644 --- a/tests/test_request_context.py +++ b/tests/test_request_context.py @@ -20,7 +20,6 @@ def test_default_request_context( assert req.context.get('note') == req.context['note'] def test_custom_request_context(self): - # Define a Request-alike with a custom context type class MyCustomContextType: pass @@ -33,7 +32,6 @@ class MyCustomRequest(Request): assert isinstance(req.context, MyCustomContextType) def test_custom_request_context_failure(self): - # Define a Request-alike with a non-callable custom context type class MyCustomRequest(Request): context_type = False diff --git a/tests/test_request_forwarded.py b/tests/test_request_forwarded.py index 66e4e406b..c4d36c39f 100644 --- a/tests/test_request_forwarded.py +++ b/tests/test_request_forwarded.py @@ -190,7 +190,6 @@ def test_forwarded_quote_escaping(asgi): ], ) def test_escape_malformed_requests(forwarded, expected_dest, asgi): - req = create_req( asgi, host='suchproxy02.suchtesting.com', diff --git a/tests/test_response_media.py b/tests/test_response_media.py index 2b4445ae6..6c19a59ea 100644 --- a/tests/test_response_media.py +++ b/tests/test_response_media.py @@ -61,7 +61,7 @@ def test_json(client, media_type): '', 'I am a \u1d0a\ua731\u1d0f\u0274 string.', ['\u2665', '\u2660', '\u2666', '\u2663'], - {'message': '\xa1Hello Unicode! \U0001F638'}, + {'message': '\xa1Hello Unicode! \U0001f638'}, { 'description': 'A collection of primitive Python type examples.', 'bool': False is not True and True is not False, @@ -71,7 +71,7 @@ def test_json(client, media_type): 'list': ['a', 'sequence', 'of', 'items'], 'none': None, 'str': 'ASCII string', - 'unicode': 'Hello Unicode! \U0001F638', + 'unicode': 'Hello Unicode! \U0001f638', }, ], ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 349032856..df5ff2fc4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -645,8 +645,8 @@ def test_secure_filename_empty_value(self): ('/api', True), ('/data/items/something?query=apples%20and%20oranges', True), ('/food?item=ð\x9f\x8d\x94', False), - ('\x00\x00\x7F\x00\x00\x7F\x00', True), - ('\x00\x00\x7F\x00\x00\x80\x00', False), + ('\x00\x00\x7f\x00\x00\x7f\x00', True), + ('\x00\x00\x7f\x00\x00\x80\x00', False), ], ) @pytest.mark.parametrize('method', ['isascii', '_isascii']) @@ -952,7 +952,7 @@ def test_query_string_in_path(self, app): '', 'I am a \u1d0a\ua731\u1d0f\u0274 string.', [1, 3, 3, 7], - {'message': '\xa1Hello Unicode! \U0001F638'}, + {'message': '\xa1Hello Unicode! \U0001f638'}, { 'count': 4, 'items': [ diff --git a/tox.ini b/tox.ini index 8ebde179e..0e6dcd477 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ # -------------------------------------------------------------------- envlist = cleanup, - blue, + ruff, pep8, mypy, mypy_tests, @@ -269,13 +269,15 @@ deps = flake8 flake8-import-order commands = flake8 [] -[testenv:blue] -deps = blue>=0.9.0 -commands = blue --check . [] +[testenv:ruff] +deps = ruff>=0.3.7 +skip_install = True +commands = ruff format --check . [] [testenv:reformat] -deps = blue>=0.9.0 -commands = blue . [] +deps = ruff>=0.3.7 +skip_install = True +commands = ruff format . [] [testenv:pep8-docstrings] deps = flake8