Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ jobs:

strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v6"
- name: Install uv (official Astral action)
uses: astral-sh/setup-uv@v5
with:
python-version: "${{ matrix.python-version }}"
allow-prereleases: true
version: "0.9.13"
enable-cache: true
python-version: ${{ matrix.python-version }}
- name: "Install dependencies"
run: "scripts/install"
- name: "Run linting checks"
Expand Down
35 changes: 35 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
Expand Down Expand Up @@ -64,6 +65,40 @@ Documentation = "https://www.python-httpx.org"
Homepage = "https://github.com/encode/httpx"
Source = "https://github.com/encode/httpx"

[dependency-groups]
dev = [
# Install httpx with all optional dependencies
{ include-group = "test" },
{ include-group = "docs" },
{ include-group = "package" },
{ include-group = "lint" },
]
test = [
# Optional charset auto-detection (used in test cases)
"chardet==5.2.0",
# Testing dependencies
"coverage[toml]==7.10.6",
"cryptography==45.0.7",
"pytest==8.4.1",
"trio==0.31.0",
"trio-typing==0.10.0",
"trustme==1.2.1",
"uvicorn==0.38.0",
]
docs = [
"mkdocs==1.6.1",
"mkautodoc==0.2.0",
"mkdocs-material==9.6.18",
]
package = [
"build==1.3.0",
"twine==6.1.0",
]
lint = [
"mypy==1.17.1",
"ruff==0.12.11",
]

[tool.hatch.version]
path = "httpx/__version__.py"

Expand Down
29 changes: 0 additions & 29 deletions requirements.txt

This file was deleted.

12 changes: 3 additions & 9 deletions scripts/build
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
#!/bin/sh -e

if [ -d 'venv' ] ; then
PREFIX="venv/bin/"
else
PREFIX=""
fi

set -x

${PREFIX}python -m build
${PREFIX}twine check dist/*
${PREFIX}mkdocs build
uv run python -m build
uv run twine check dist/*
uv run mkdocs build
16 changes: 9 additions & 7 deletions scripts/check
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#!/bin/sh -e

export PREFIX=""
if [ -d 'venv' ] ; then
export PREFIX="venv/bin/"
fi
export SOURCE_FILES="httpx tests"

set -x

./scripts/sync-version
${PREFIX}ruff format $SOURCE_FILES --diff
${PREFIX}mypy $SOURCE_FILES
${PREFIX}ruff check $SOURCE_FILES

echo "==> Checking formatting…"
uv run ruff format $SOURCE_FILES --diff

echo "==> Running type checks and linting…"
uv run mypy $SOURCE_FILES

echo "==> Running linting checks…"
uv run ruff check $SOURCE_FILES
6 changes: 1 addition & 5 deletions scripts/coverage
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
#!/bin/sh -e

export PREFIX=""
if [ -d 'venv' ] ; then
export PREFIX="venv/bin/"
fi
export SOURCE_FILES="httpx tests"

set -x

${PREFIX}coverage report --show-missing --skip-covered --fail-under=100
uv run coverage report --show-missing --skip-covered --fail-under=100
7 changes: 1 addition & 6 deletions scripts/docs
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
#!/bin/sh -e

export PREFIX=""
if [ -d 'venv' ] ; then
export PREFIX="venv/bin/"
fi

set -x

${PREFIX}mkdocs serve
uv run mkdocs serve
19 changes: 2 additions & 17 deletions scripts/install
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
#!/bin/sh -e

# Use the Python executable provided from the `-p` option, or a default.
[ "$1" = "-p" ] && PYTHON=$2 || PYTHON="python3"

REQUIREMENTS="requirements.txt"
VENV="venv"

set -x

if [ -z "$GITHUB_ACTIONS" ]; then
"$PYTHON" -m venv "$VENV"
PIP="$VENV/bin/pip"
else
PIP="pip"
fi

"$PIP" install -U pip
"$PIP" install -r "$REQUIREMENTS"
echo "==> Installing Python dependencies…"
uv sync --all-extras
11 changes: 5 additions & 6 deletions scripts/lint
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#!/bin/sh -e

export PREFIX=""
if [ -d 'venv' ]; then
export PREFIX="venv/bin/"
fi
export SOURCE_FILES="httpx tests"

set -x

${PREFIX}ruff check --fix $SOURCE_FILES
${PREFIX}ruff format $SOURCE_FILES
echo "==> Running linting checks…"
uv run ruff check --fix $SOURCE_FILES

echo "==> Formatting source code…"
uv run ruff format $SOURCE_FILES
9 changes: 2 additions & 7 deletions scripts/publish
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

VERSION_FILE="httpx/__version__.py"

if [ -d 'venv' ] ; then
PREFIX="venv/bin/"
else
PREFIX=""
fi

if [ ! -z "$GITHUB_ACTIONS" ]; then
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
Expand All @@ -22,5 +17,5 @@ fi

set -x

${PREFIX}twine upload dist/*
${PREFIX}mkdocs gh-deploy --force
uv run twine upload dist/*
uv run mkdocs gh-deploy --force
7 changes: 1 addition & 6 deletions scripts/test
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
#!/bin/sh

export PREFIX=""
if [ -d 'venv' ] ; then
export PREFIX="venv/bin/"
fi

set -ex

if [ -z $GITHUB_ACTIONS ]; then
scripts/check
fi

${PREFIX}coverage run -m pytest "$@"
uv run coverage run -m pytest "$@"

if [ -z $GITHUB_ACTIONS ]; then
scripts/coverage
Expand Down
17 changes: 12 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,9 @@ def install_signal_handlers(self) -> None:
async def serve(self, sockets=None):
self.restart_requested = asyncio.Event()

loop = asyncio.get_event_loop()
tasks = {
loop.create_task(super().serve(sockets=sockets)),
loop.create_task(self.watch_restarts()),
asyncio.create_task(super().serve(sockets=sockets)),
asyncio.create_task(self.watch_restarts()),
}
await asyncio.wait(tasks)

Expand Down Expand Up @@ -269,7 +268,15 @@ async def watch_restarts(self) -> None: # pragma: no cover


def serve_in_thread(server: TestServer) -> typing.Iterator[TestServer]:
thread = threading.Thread(target=server.run)
def run_server() -> None:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(server.serve()) # type: ignore
finally:
loop.close()

thread = threading.Thread(target=run_server)
thread.start()
try:
while not server.started:
Expand All @@ -282,6 +289,6 @@ def serve_in_thread(server: TestServer) -> typing.Iterator[TestServer]:

@pytest.fixture(scope="session")
def server() -> typing.Iterator[TestServer]:
config = Config(app=app, lifespan="off", loop="asyncio")
config = Config(app=app, lifespan="off")
server = TestServer(config=config)
yield from serve_in_thread(server)
2 changes: 2 additions & 0 deletions tests/test_timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ async def test_read_timeout(server):
await client.get(server.url.copy_with(path="/slow_response"))


# TODO: Fix ResourceWarning in this test for Python>=3.14
@pytest.mark.filterwarnings("ignore::ResourceWarning")
@pytest.mark.anyio
async def test_write_timeout(server):
timeout = httpx.Timeout(None, write=1e-6)
Expand Down
Loading