Skip to main content

curlify3

Convert request objects from popular Python HTTP libraries into ready-to-run curl commands.

PyPI Downloads Python Tests

curlify3 takes a request object from any supported client or server framework and renders it as an equivalent curl command — useful for logging, debugging, sharing reproductions, and copy-pasting from your IDE into a terminal.

Features

  • Single dispatch entrypoint — to_curl() (sync) and to_curl_async() (async)
  • Works with client-side requests (requests, httpx, httpx2) and server-side incoming requests (aiohttp.web, starlette / fastapi)
  • Faithful rendering of headers, query parameters, cookies (-b), and bodies
  • Body payloads: text, JSON, form-encoded, multipart, binary
  • POSIX shell output by default, Windows PowerShell output with shell="powershell"
  • One-line output by default, multi-line with pretty=True and long option names with long_options=True
  • Zero runtime dependencies
  • Fully annotated and py.typed, so the types reach your own type checker

Installation

pip install curlify3

Requires Python 3.10+.

Comparison with curlify and curlify2

curlify curlify2 curlify3
requests [x] [x] [x]
httpx [ ] [x] [x]
httpx2 (HTTP/2) [ ] [ ] [x]
aiohttp (server-side) [ ] [ ] [x]
starlette / fastapi (server-side) [ ] [ ] [x]
Async API [ ] [ ] [x]
Python 3.7+ 3.7–3.11 3.10+

curlify is the original and covers only requests. curlify2 added httpx but is sync-only, client-side-only, and has not seen a release since 2023. curlify3 extends the same idea with HTTP/2 (httpx2), an async entrypoint, and server-side adapters for aiohttp and starlette / fastapi so you can dump incoming requests as curl from inside a handler.

Quick start

import requests
from curlify3 import to_curl

response = requests.get("https://httpbin.org/get")
print(to_curl(response.request))
# curl -H 'user-agent: python-requests/2.32.3' -H 'accept-encoding: gzip, deflate' \
#      -H 'accept: */*' -H 'connection: keep-alive' https://httpbin.org/get

Usage

requests

import requests
from curlify3 import to_curl

req = requests.Request(
    "POST",
    "https://httpbin.org/post",
    json={"hello": "world"},
).prepare()

print(to_curl(req))

httpx (sync)

import httpx
from curlify3 import to_curl

req = httpx.Request("POST", "https://httpbin.org/post", json={"hello": "world"})
print(to_curl(req))

httpx (async)

import asyncio
import httpx
from curlify3 import to_curl_async

async def main():
    req = httpx.Request("POST", "https://httpbin.org/post", json={"a": 1})
    print(await to_curl_async(req))

asyncio.run(main())

httpx2 — HTTP/2

The generated command includes --http2.

import httpx2
from curlify3 import to_curl

req = httpx2.Request("GET", "https://httpbin.org/get")
print(to_curl(req))
# curl --http2 -H 'host: httpbin.org' https://httpbin.org/get

to_curl_async() works with httpx2.Request too.

Readable output

pretty=True puts every option on its own line, and long_options=True spells the options out (--header instead of -H). They are independent, so either can be used alone.

import requests
from curlify3 import to_curl

req = requests.Request(
    "POST",
    "https://httpbin.org/post",
    json={"date": "2026-08-10"},
).prepare()

print(to_curl(req, pretty=True, long_options=True))
# curl https://httpbin.org/post \
#   --request POST \
#   --header 'content-type: application/json' \
#   --data '{"date": "2026-08-10"}'

The url moves to the first line, where curl reads it just as well as in the trailing position — the same layout Chrome DevTools' "Copy as cURL" produces. A request with no options stays on one line.

pretty=True is rejected with a ValueError for shell="powershell": the --% token that dialect relies on is effective only until the next newline, and a backtick cannot extend it, so a multi-line command would be passed to curl.exe in pieces.

Windows PowerShell

By default the command is formatted for POSIX shells. Pass shell="powershell" to get one that pastes into Windows PowerShell 5.1.

import requests
from curlify3 import to_curl

req = requests.Request(
    "POST",
    "https://httpbin.org/post",
    json={"date": "2026-08-10"},
).prepare()

print(to_curl(req, shell="powershell"))
# curl.exe --% -X POST -H "content-type: application/json" -d "{\"date\": \"2026-08-10\"}" "https://httpbin.org/post"

curl.exe avoids the Invoke-WebRequest alias, and --% — PowerShell's stop-parsing token — hands the rest to curl.exe verbatim. The token is what makes arbitrary JSON survive: without it, 5.1's argument binder re-quotes values by counting every double quote, escaped or not, and mangles the body. Two consequences worth knowing:

  • %NAME% environment-variable references in a payload are still expanded.
  • The command is for PowerShell only — in cmd, git-bash, or WSL, curl.exe chokes on --%; use the default shell="sh" output there. On pwsh 7.2+, run $PSNativeCommandArgumentPassing = 'Legacy' in the session first.

The constants curlify3.SH and curlify3.POWERSHELL are exported for use instead of the raw strings.

aiohttp — server-side

Render an incoming request inside a handler. The async variant is required because the body is read from the stream.

from aiohttp import web
from curlify3 import to_curl_async

async def handler(request: web.Request) -> web.Response:
    curl = await to_curl_async(request)
    print(curl)
    return web.json_response({"ok": True})

starlette / fastapi — server-side

from fastapi import FastAPI, Request
from curlify3 import to_curl_async

app = FastAPI()

@app.post("/echo")
async def echo(request: Request):
    curl = await to_curl_async(request)
    return {"curl": curl}

API

to_curl(request, shell="sh", pretty=False, long_options=False) -> str

Render a request object as a curl command. Use for synchronous request types (requests.PreparedRequest, httpx.Request, httpx2.Request).

to_curl_async(request, shell="sh", pretty=False, long_options=False) -> str

Async variant. Use for server-side request objects whose body must be await-ed (aiohttp.web.Request, starlette.requests.Request) or when you prefer the async pathway for httpx / httpx2.

shell selects the output dialect: "sh" (default, POSIX shells) or "powershell" (Windows PowerShell 5.1; for pwsh 7.2+ see the PowerShell section). pretty breaks the command across lines, long_options spells the options out; both default to False, which keeps the output on a single line with short options.

Both functions raise ValueError if the request type or the shell value is not recognized, or if pretty=True is combined with shell="powershell".

Supported request objects

Library Type to_curl to_curl_async Notes
requests PreparedRequest [x] [ ] Pass Request(...).prepare()
httpx httpx.Request [x] [x]
httpx2 httpx2.Request [x] [x] Adds --http2
aiohttp aiohttp.web.Request [ ] [x] Server-side
starlette / fastapi starlette.requests.Request [ ] [x] Server-side

Payload handling

Payload Rendered as
Plain text -d 'text'
JSON -d '{"k":"v"}' with content-type: application/json
Form-encoded -d 'k=v&k2=v2' with content-type: application/x-www-form-urlencoded
Multipart / files -F 'field=@file' -F 'other=value'
Binary -d with raw bytes (falls back when not UTF-8)
Cookies -b 'k=v' (lifted out of Cookie header)
Headers -H 'name: value' (lowercased)

Content-Length is dropped. If a body is present without Content-Type, content-type: plain/text is added so curl does not guess.

Development

The project uses uv and just.

uv sync --group dev
just tests   # pytest
just lint    # ruff format --check, ruff check, ty check
just fmt     # ruff format, ruff check --fix

Formatting and linting are handled by ruff, type checking by ty.

One convention the tooling cannot enforce on its own: every parameter of a function goes on its own line, which means every parameter list ends with a trailing comma. Write the comma and the formatter keeps the layout.

CI runs the linter and the type checker on every pull request, and the test suite on Python 3.10–3.14.

Changelog

See CHANGELOG.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

curlify3-0.10.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

curlify3-0.10-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file curlify3-0.10.tar.gz.

File metadata

  • Download URL: curlify3-0.10.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for curlify3-0.10.tar.gz
Algorithm Hash digest
SHA256 80998d6acae42e0bdb5cdbfdd03be5bcf3e3cdd59eeff2a9836f5ced62d5bc64
MD5 3d524ee9f276d61e711be874bfe59d0e
BLAKE2b-256 2f236510f545ec222a57beddb3bd726c57ea9925a2de6d1aee8526f95cdea993

See more details on using hashes here.

File details

Details for the file curlify3-0.10-py3-none-any.whl.

File metadata

  • Download URL: curlify3-0.10-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for curlify3-0.10-py3-none-any.whl
Algorithm Hash digest
SHA256 caefbc7dd9b6a087f1c26e237dc1785e81fa4379fa445006b44e6317cb149c13
MD5 e75221a7281a014d987e5aa021bcc8f7
BLAKE2b-256 432149ee49fff6f13e9310df0b6bb23269b954441f6f8935f3f1d2989b33d97d

See more details on using hashes here.

Release history Release notifications | RSS feed

0.13

2 files

0.12

2 files

0.11

2 files

This release

0.10 This release

2 files

0.9

2 files

0.8

2 files

0.7

2 files

0.6

2 files

0.5

2 files

0.4

2 files

0.3

2 files

0.2

2 files

0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page