Files
kennethreitz 0cbcaf9c4f Code quality improvements and test fixes (#592)
## Summary
Comprehensive post-v3.0.0 modernization: new features, bug fixes,
dependency cleanup, docs, and test coverage.

**New features:**
- **HTTP method filtering** — `@api.route("/data", methods=["GET"])`
- **Lifespan context manager** — modern async startup/shutdown
- **`api.exception_handler()`** — custom error handling per exception
type
- **`api.graphql()`** — one-liner GraphQL setup
- **`resp.file()`** — serve files from disk with auto content-type
- **before_request short-circuit** — set status code to skip route
handler
- **`req.path_params`** / **`req.client`** / **`req.is_json`** — new
request properties
- **`uuid`** and **`path`** route convertors
- **PEP 561 `py.typed`** marker

**Bug fixes:**
- Fix multipart parser losing headers
- Fix `url_for()` with typed params (`{id:int}`)
- Fix `resp.body` encoding crash on bytes content
- Fix Python 3.9 type syntax (`from __future__ import annotations`)
- Fix broken session test and no-op file upload test
- Fix helloworld example 404 on root path

**Dependencies:**
- Flattened — `pip install responder` gets everything
- Core: just starlette + uvicorn (down from 10 deps)

**Docs & README:**
- All new features documented in tour
- Modernized README features list
- Deployment guide: Docker, cloud, uvicorn
- Removed Pipenv, extras, stale references throughout

**Tests & quality:**
- 117 tests (up from 92), 91% coverage, 0 warnings
- CaseInsensitiveDict, GraphQL edge cases, staticfiles tests
- Ruff clean, all `tmpdir` → `tmp_path`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:44:11 -04:00

95 lines
2.5 KiB
Python

import inspect
import pytest
from responder import models
from responder.models import CaseInsensitiveDict
_default_query = "q=%7b%20hello%20%7d&name=myname&user_name=test_user"
@pytest.mark.parametrize(
"query, expected",
[
pytest.param(
_default_query,
{"q": ["{ hello }"], "name": ["myname"], "user_name": ["test_user"]},
id="parse query with unique keys",
),
pytest.param(
"q=1&q=2&q=3", {"q": ["1", "2", "3"]}, id="parse query with the same key"
),
],
)
def test_query_dict(query, expected):
d = models.QueryDict(query)
assert d == expected
def test_query_dict_get():
d = models.QueryDict(_default_query)
assert d["user_name"] == "test_user"
assert d.get("key_none_exist") is None
def test_query_dict_get_list():
d = models.QueryDict(_default_query)
assert d.get_list("user_name") == ["test_user"]
assert d.get_list("key_none_exist") == []
assert d.get_list("key_none_exist", ["foo"]) == ["foo"]
def test_query_dict_items_list():
d = models.QueryDict(_default_query)
items_list = d.items_list()
assert inspect.isgenerator(items_list)
assert dict(items_list) == {
"q": ["{ hello }"],
"name": ["myname"],
"user_name": ["test_user"],
}
def test_query_dict_items():
d = models.QueryDict(_default_query)
items = d.items()
assert inspect.isgenerator(items)
assert dict(items) == {"q": "{ hello }", "name": "myname", "user_name": "test_user"}
class TestCaseInsensitiveDict:
def test_set_and_get(self):
d = CaseInsensitiveDict()
d["Content-Type"] = "text/html"
assert d["content-type"] == "text/html"
assert d["CONTENT-TYPE"] == "text/html"
def test_contains(self):
d = CaseInsensitiveDict()
d["X-Custom"] = "value"
assert "x-custom" in d
assert "X-CUSTOM" in d
assert "missing" not in d
def test_get_default(self):
d = CaseInsensitiveDict()
assert d.get("missing") is None
assert d.get("missing", "default") == "default"
d["Key"] = "val"
assert d.get("KEY") == "val"
def test_update(self):
d = CaseInsensitiveDict()
d.update({"Content-Type": "text/html", "Accept": "json"})
assert d["content-type"] == "text/html"
assert d["accept"] == "json"
def test_update_kwargs(self):
d = CaseInsensitiveDict()
d.update(key1="val1", key2="val2")
assert d["key1"] == "val1"