Chore: Code formatting (#594)

## About
A few cosmetic adjustments aka. code formatting.
Also validate the outcome on CI/GHA.
Feel free to improve now or later at your disposal.

## Details
The updates are based on using the most recent versions of pyproject-fmt
and ruff.
Specifically, spots marked with `noqa` might need further love, also at
your disposal.

---------

Co-authored-by: Kenneth Reitz <me@kennethreitz.org>
This commit is contained in:
Andreas Motl
2026-03-24 20:21:04 +01:00
committed by GitHub
parent a375984310
commit ff6d530338
11 changed files with 120 additions and 122 deletions
-2
View File
@@ -8,10 +8,8 @@ import responder
@asynccontextmanager
async def lifespan(app):
# Startup: initialize resources
print("Starting up...")
yield
# Shutdown: clean up resources
print("Shutting down...")
api = responder.API(lifespan=lifespan)
+9 -3
View File
@@ -1,8 +1,9 @@
# Complete REST API example with Pydantic validation.
# https://responder.kennethreitz.org/tutorial-rest.html
import responder
from pydantic import BaseModel
import responder
class BookIn(BaseModel):
title: str
@@ -35,8 +36,13 @@ def list_books(req, resp):
resp.media = list(books_db.values())
@api.route("/books", methods=["POST"], check_existing=False,
request_model=BookIn, response_model=BookOut)
@api.route(
"/books",
methods=["POST"],
check_existing=False,
request_model=BookIn,
response_model=BookOut,
)
async def create_book(req, resp):
global next_id
data = await req.media()
+2 -2
View File
@@ -35,7 +35,7 @@ def index(req, resp):
</script>
</body>
</html>
"""
""" # noqa: E501
@api.route("/chat", websocket=True)
@@ -47,7 +47,7 @@ async def chat(ws):
message = await ws.receive_text()
for client in connected:
await client.send_text(message)
except Exception:
except Exception: # noqa: S110
pass
finally:
connected.discard(ws)
+49 -68
View File
@@ -20,7 +20,7 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
@@ -34,7 +34,7 @@ classifiers = [
dynamic = [ "version" ]
dependencies = [
"a2wsgi",
"apispec>=1.0.0",
"apispec>=1",
"chardet",
"docopt-ng",
"graphene>=3",
@@ -44,17 +44,15 @@ dependencies = [
"pueblo[sfa-full]>=0.0.11",
"pydantic>=2",
"python-multipart",
"starlette[full]>=1.0",
"starlette[full]>=1",
"uvicorn[standard]",
]
[project.optional-dependencies]
develop = [
optional-dependencies.develop = [
"pyproject-fmt",
"ruff",
"validate-pyproject",
]
docs = [
optional-dependencies.docs = [
"alabaster<1.1",
"myst-parser",
"sphinx>=5,<9",
@@ -62,8 +60,8 @@ docs = [
"sphinx-copybutton",
"sphinx-design-elements",
]
release = ["build", "twine"]
test = [
optional-dependencies.release = [ "build", "twine" ]
optional-dependencies.test = [
"flask",
"mypy",
"pytest",
@@ -71,32 +69,22 @@ test = [
"pytest-mock",
"pytest-rerunfailures",
]
urls.Documentation = "https://responder.kennethreitz.org"
urls.Homepage = "https://github.com/kennethreitz/responder"
urls.Issues = "https://github.com/kennethreitz/responder/issues"
urls.Repository = "https://github.com/kennethreitz/responder"
scripts.responder = "responder.ext.cli:cli"
[project.scripts]
responder = "responder.ext.cli:cli"
[project.urls]
Homepage = "https://github.com/kennethreitz/responder"
Documentation = "https://responder.kennethreitz.org"
Repository = "https://github.com/kennethreitz/responder"
Issues = "https://github.com/kennethreitz/responder/issues"
[tool.setuptools.dynamic]
version = {attr = "responder.__version__.__version__"}
[tool.setuptools.package-data]
responder = ["py.typed", "ext/openapi/docs/*.html"]
[tool.setuptools.packages.find]
exclude = ["tests"]
[tool.setuptools]
dynamic.version = { attr = "responder.__version__.__version__" }
package-data.responder = [ "py.typed", "ext/openapi/docs/*.html" ]
packages.find.exclude = [ "tests" ]
[tool.ruff]
line-length = 90
extend-exclude = [
"docs/source/conf.py",
]
lint.select = [
# Builtins
"A",
@@ -124,59 +112,20 @@ lint.select = [
# flake8-2020
"YTT",
]
lint.extend-ignore = [
"S101", # Allow use of `assert`.
]
lint.per-file-ignores."responder/util/cmd.py" = [ "A005" ] # Module shadows a Python standard-library module
lint.per-file-ignores."tests/*" = [
"ERA001", # Found commented-out code.
"S101", # Allow use of `assert`, and `print`.
]
[tool.pytest.ini_options]
addopts = """
-rfEXs -p pytester --strict-markers --verbosity=3
--cov --cov-report=term-missing --cov-report=xml
"""
filterwarnings = [
"error::UserWarning",
]
log_level = "DEBUG"
log_cli_level = "DEBUG"
log_format = "%(asctime)-15s [%(name)-36s] %(levelname)-8s: %(message)s"
minversion = "2.0"
testpaths = [
"responder",
"tests",
]
markers = [
]
xfail_strict = true
[tool.coverage.run]
branch = false
omit = [
"*.html",
"tests/*",
]
[tool.coverage.report]
fail_under = 0
show_missing = true
exclude_lines = [
"# pragma: no cover",
"raise NotImplemented",
]
[tool.mypy]
packages = [
"responder",
]
exclude = [
]
exclude = []
check_untyped_defs = true
explicit_package_bases = true
ignore_missing_imports = true
@@ -184,3 +133,35 @@ implicit_optional = true
install_types = true
namespace_packages = true
non_interactive = true
[tool.pytest]
ini_options.addopts = """
-rfEXs -p pytester --strict-markers --verbosity=3
--cov --cov-report=term-missing --cov-report=xml
"""
ini_options.filterwarnings = [
"error::UserWarning",
]
ini_options.log_level = "DEBUG"
ini_options.log_cli_level = "DEBUG"
ini_options.log_format = "%(asctime)-15s [%(name)-36s] %(levelname)-8s: %(message)s"
ini_options.minversion = "2.0"
ini_options.testpaths = [
"responder",
"tests",
]
ini_options.markers = []
ini_options.xfail_strict = true
[tool.coverage]
run.branch = false
run.omit = [
"*.html",
"tests/*",
]
report.exclude_lines = [
"# pragma: no cover",
"raise NotImplemented",
]
report.fail_under = 0
report.show_missing = true
+2 -4
View File
@@ -161,9 +161,7 @@ class API:
import uuid as _uuid
def _add_request_id(req, resp):
rid = req.headers.get(
"X-Request-ID", str(_uuid.uuid4())
)
rid = req.headers.get("X-Request-ID", str(_uuid.uuid4()))
resp.headers["X-Request-ID"] = rid
self.router.after_request(_add_request_id)
@@ -562,7 +560,7 @@ class API:
"""Run the application. Shorthand for :meth:`serve` that inherits the ``debug`` setting.
:param kwargs: Keyword arguments passed through to :meth:`serve`.
"""
""" # noqa: E501
if "debug" not in kwargs:
kwargs.update({"debug": self.debug})
self.serve(**kwargs)
+3 -1
View File
@@ -129,7 +129,9 @@ class OpenAPISchema:
op["requestBody"] = {
"content": {
"application/json": {
"schema": {"$ref": f"#/components/schemas/{model_name}"}
"schema": {
"$ref": f"#/components/schemas/{model_name}"
}
}
}
}
+1 -3
View File
@@ -38,9 +38,7 @@ class RateLimiter:
def _cleanup(self, key):
now = time.time()
cutoff = now - self.period
self._buckets[key] = [
t for t in self._buckets[key] if t > cutoff
]
self._buckets[key] = [t for t in self._buckets[key] if t > cutoff]
def check(self, req, resp):
"""Check rate limit. Sets 429 status if exceeded."""
+1 -1
View File
@@ -192,7 +192,7 @@ class Route(BaseRoute):
try:
validated = resp_model(**response.media)
response.media = validated.model_dump()
except Exception:
except Exception: # noqa: S110
pass # Don't break the response if serialization fails
# Run after-request hooks
+14 -11
View File
@@ -4,14 +4,14 @@ import time
import pytest
from starlette.testclient import TestClient as StarletteTestClient
from starlette.websockets import WebSocketDisconnect
import responder
from responder.background import BackgroundQueue
from responder.models import CaseInsensitiveDict, QueryDict, Response
from responder.models import QueryDict
from responder.routes import Route, WebSocketRoute
from responder.templates import Templates
# --- api.py coverage ---
@@ -78,7 +78,7 @@ def test_background_task_exception(capsys):
raise ValueError("task failed")
future = failing_task()
future.result # wait for completion
future.result # wait for completion # noqa: B018
time.sleep(0.2) # let the done callback fire
captured = capsys.readouterr()
@@ -302,7 +302,7 @@ def test_yaml_content_negotiation(api):
def test_websocket_404(api):
"""Lines 308-310: WebSocket to unknown route gets closed."""
client = StarletteTestClient(api)
with pytest.raises(Exception):
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("ws://;/nonexistent"):
pass
@@ -325,9 +325,7 @@ def test_websocket_route_params():
pass
route = WebSocketRoute("/ws/{room_id:int}", handler)
matches, scope = route.matches(
{"type": "websocket", "path": "/ws/42"}
)
matches, scope = route.matches({"type": "websocket", "path": "/ws/42"})
assert matches is True
assert scope["path_params"] == {"room_id": 42}
@@ -591,7 +589,10 @@ def test_pydantic_schema():
from pydantic import BaseModel
api = responder.API(
title="Test", version="1.0", openapi="3.0.2", allowed_hosts=[";"],
title="Test",
version="1.0",
openapi="3.0.2",
allowed_hosts=[";"],
)
@api.schema("Pet")
@@ -611,7 +612,10 @@ def test_pydantic_request_response_models():
from pydantic import BaseModel
api = responder.API(
title="Test", version="1.0", openapi="3.0.2", allowed_hosts=[";"],
title="Test",
version="1.0",
openapi="3.0.2",
allowed_hosts=[";"],
)
class ItemIn(BaseModel):
@@ -623,8 +627,7 @@ def test_pydantic_request_response_models():
name: str
price: float
@api.route("/items", methods=["POST"],
request_model=ItemIn, response_model=ItemOut)
@api.route("/items", methods=["POST"], request_model=ItemIn, response_model=ItemOut)
async def create(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
+24 -10
View File
@@ -109,10 +109,13 @@ def test_graphql_error_response(api, schema):
def test_graphql_variables_json(api, schema):
"""Variables passed via JSON body."""
api.add_route("/", GraphQLView(schema=schema, api=api))
r = api.requests.post("http://;/", json={
r = api.requests.post(
"http://;/",
json={
"query": "query Hello($name: String!) { hello(name: $name) }",
"variables": {"name": "Alice"},
})
},
)
assert r.json() == {"data": {"hello": "Hello Alice"}}
@@ -121,7 +124,8 @@ def test_graphql_variables_query_param(api, schema):
api.add_route("/", GraphQLView(schema=schema, api=api))
variables = json.dumps({"name": "Bob"})
r = api.requests.get(
f"http://;/?query=query Hello($name: String!) {{ hello(name: $name) }}&variables={variables}",
f"http://;/?query=query Hello($name: String!) "
f"{{ hello(name: $name) }}&variables={variables}",
headers={"Accept": "json"},
)
assert r.json() == {"data": {"hello": "Hello Bob"}}
@@ -134,10 +138,13 @@ def test_graphql_operation_name_json(api, multi_op_schema):
query SayHello { hello }
query SayGoodbye { goodbye }
"""
r = api.requests.post("http://;/", json={
r = api.requests.post(
"http://;/",
json={
"query": query,
"operationName": "SayHello",
})
},
)
data = r.json()
assert data["data"]["hello"] == "Hello stranger"
@@ -157,9 +164,12 @@ def test_graphql_operation_name_query_param(api, multi_op_schema):
def test_graphql_mutation(api, mutation_schema):
"""Mutations work via JSON body."""
api.add_route("/", GraphQLView(schema=mutation_schema, api=api))
r = api.requests.post("http://;/", json={
r = api.requests.post(
"http://;/",
json={
"query": 'mutation { createUser(name: "Eve") { ok name } }',
})
},
)
data = r.json()
assert data["data"]["createUser"]["ok"] is True
assert data["data"]["createUser"]["name"] == "Eve"
@@ -168,10 +178,14 @@ def test_graphql_mutation(api, mutation_schema):
def test_graphql_mutation_with_variables(api, mutation_schema):
"""Mutations with variables."""
api.add_route("/", GraphQLView(schema=mutation_schema, api=api))
r = api.requests.post("http://;/", json={
"query": "mutation CreateUser($name: String!) { createUser(name: $name) { ok name } }",
r = api.requests.post(
"http://;/",
json={
"query": "mutation CreateUser($name: String!) "
"{ createUser(name: $name) { ok name } }",
"variables": {"name": "Frank"},
})
},
)
data = r.json()
assert data["data"]["createUser"]["ok"] is True
assert data["data"]["createUser"]["name"] == "Frank"
+5 -7
View File
@@ -1,13 +1,10 @@
"""Tests for new features: validation, SSE, after_request, route groups, etc."""
import pytest
from pydantic import BaseModel
from starlette.testclient import TestClient as StarletteTestClient
import responder
from responder.ext.ratelimit import RateLimiter
# --- Pydantic auto-validation ---
@@ -42,7 +39,9 @@ def test_pydantic_request_validation():
assert "errors" in r.json()
# Invalid request — wrong type
r = api.requests.post("http://;/items", json={"name": "widget", "price": "not_a_number"})
r = api.requests.post(
"http://;/items", json={"name": "widget", "price": "not_a_number"}
)
assert r.status_code == 422
@@ -50,8 +49,7 @@ def test_pydantic_response_serialization():
"""Auto-serialize response through response_model."""
api = responder.API(allowed_hosts=[";"])
@api.route("/items", methods=["POST"],
request_model=ItemIn, response_model=ItemOut)
@api.route("/items", methods=["POST"], request_model=ItemIn, response_model=ItemOut)
async def create(req, resp):
data = await req.media()
# Include an extra field that should be stripped by the model
@@ -257,7 +255,7 @@ def test_rate_limiter():
def view(req, resp):
resp.text = "ok"
for i in range(3):
for _i in range(3):
r = api.requests.get("http://;/")
assert r.status_code == 200
assert "X-RateLimit-Remaining" in r.headers