Skip to main content

hammock-httpx

 _                                   _
| |                                 | |
| |__  _____ ____  ____   ___   ____| |  _
|  _ \(____ |    \|    \ / _ \ / ___) |_/ )
| | | / ___ | | | | | | | |_| ( (___|  _ (
|_| |_\_____|_|_|_|_|_|_|\___/ \____)_| \_)

Rest like a boss — chainable, typed wrapper over httpx for REST.

Fork notice: This is a fork of kadirpekel/hammock (original by Kadir Pekel). It was refactored with AI assistance to modernize the codebase to httpx and address several long-standing issues and PRs: requestshttpx, fully typed (py.typed, Self/Unpack, HttpxClientKwargs/HammockRequestKwargs), sync Hammock + async AsyncHammock, QUERY (RFC 10008) + TRACE/CONNECT, resource-URI slash stripping (PR #13/#16), custom session/client injection (#14), redirect verb preservation (#21), src layout, uv/pyproject.toml, and expanded tests.

Distribution: hammock-httpx on PyPI, import remains import hammock / from hammock import Hammock for backwards compat.

Hammock lets you turn any REST API into a dead-simple programmatic API by mapping URL segments to Python attributes and calls. No manual string formatting, full reuse of base URLs, sync and async.

httpx is used under the hood, so you get modern TLS, HTTP/2, connection pooling, timeouts and auth for free.

Features

  • Chainable URL buildingapi.users("foo").posts("bar").comments.GET()http://.../users/foo/posts/bar/comments
  • Sync + AsyncHammock (httpx.Client) and AsyncHammock (httpx.AsyncClient)
  • Typed — PEP 561 py.typed, ty + ruff clean, supports 3.9+
  • All verbsGET HEAD OPTIONS POST PUT PATCH DELETE TRACE CONNECT QUERY (QUERY per RFC 10008)
  • Resource URIsapi("/api/v1/users/4711/") correctly strips leading/trailing / (no //)
  • Custom sessions — inject your own httpx.Client/AsyncClient (OAuth, custom transports)
  • Trailing slashappend_slash=True
  • Redirect-safe — preserves POST/PUT/PATCH on 301/302 (issue #21), 303GET
  • src layout + uvsrc/hammock/ package, pyproject.toml, uv dev

Install

# pip (new distribution name)
pip install hammock-httpx
# import stays compatible:
# import hammock; from hammock import Hammock, AsyncHammock

# uv
uv add hammock-httpx

# from source (uv)
uv sync

Requires Python >=3.9 and httpx>=0.27. Import package is still hammock (src/hammock).

Quickstart — GitHub API

from hammock import Hammock

github = Hammock("https://api.github.com")

# GET /repos/kadirpekel/hammock/watchers
resp = github.repos("kadirpekel", "hammock").watchers.GET()
for watcher in resp.json():
    print(watcher["login"])

# PUT with auth / headers
resp = github.user.watched("kadirpekel", "hammock").PUT(
    auth=("user", "pass"),
    headers={"content-length": "0"},
)
print(resp.status_code)  # 204

Same with async:

import asyncio
from hammock import AsyncHammock


async def main():
    async with AsyncHammock("https://api.github.com") as github:
        resp = await github.repos("kadirpekel", "hammock").watchers.GET()
        print(resp.json())


asyncio.run(main())

How it works

Hammock is a thin wrapper over httpx. Attribute access and () build the URL, upper-cased HTTP verbs execute it and return an httpx.Response.

All of these make the same request to http://localhost:8000/users/foo/posts/bar/comments:

import hammock

api = hammock.Hammock("http://localhost:8000")

api.users("foo").posts("bar").comments.GET()
api.users.foo.posts("bar").GET("comments")
api.users.foo.posts.bar.comments.GET()
api.users("foo", "posts", "comments").GET()
api("users")("foo", "posts").GET("bar", "comments")
# any other combination

Signature of every verb is Hammock.VERB(*args, **kwargs) where *args are extra path components and **kwargs are passed straight to httpx (params, headers, json, content, timeout, follow_redirects, …). Return type is always httpx.Response.

Available verbs: GET HEAD OPTIONS POST PUT PATCH DELETE TRACE CONNECT QUERY (lower-cased list in Hammock.HTTP_METHODS, bound as upper-cased methods).

Real-world example

import hammock

twitter = hammock.Hammock("https://api.twitter.com/1")
resp = twitter.statuses("user_timeline.json").GET(
    params={"screen_name": "kadirpekel", "count": "10"}
)
for tweet in resp.json():
    print(tweet["text"])

Sessions & Auth

Pass any httpx.Client option to the constructor – it is forwarded to the underlying client. The client is shared across the whole chain (shallow copy via copy.copy).

import hammock
import httpx

# Basic auth reused across requests
jira = hammock.Hammock(
    "https://jira.atlassian.com/rest/api/latest",
    auth=("user", "pass"),
)

issue = jira.issue("JRA-9").GET()  # auth reused
watched = jira.issue("JRA-9").watchers.POST(params={"name": "user"})
print(watched)

# Custom client (OAuth, custom transport, headers, etc.)
client = httpx.Client(headers={"X-Sess": "1"}, auth=("user", "pass"))
api = hammock.Hammock("https://api.example.com", session=client)  # or client=client
# or: client=httpx.AsyncClient(...) for AsyncHammock

All httpx.Client kwargs are supported: headers, params, auth, cookies, timeout, follow_redirects, max_redirects, verify, … Invalid kwargs raise AttributeError.

Chain shares the session:

api = Hammock("http://example.com", headers={"X-Test": "1"})
assert api.foo._client is api.bar._client is api._session

Close when done (or use context manager for async):

api._close_session()  # sync
await api.close()  # async
async with AsyncHammock("...") as api:
    ...

Resource URIs

APIs often return resource URIs like "/api/v1/users/4711/". Pass them directly – Hammock strips leading/trailing slashes so you never get //:

api = Hammock("http://localhost:8000")
uri = "/api/v1/users/4711/"
print(api(uri))  # http://localhost:8000/api/v1/users/4711
# with trailing slash preserved if you need it:
api_slash = Hammock("http://localhost:8000", append_slash=True)
print(api_slash(uri))  # http://localhost:8000/api/v1/users/4711/

Trailing slash

api = hammock.Hammock("http://localhost:8000", append_slash=True)
print(api.foo.bar)  # http://localhost:8000/foo/bar/

Redirects

By default follow_redirects=True and Hammock follows redirects manually preserving the original verb for 301/302/307/308 (issue #21). POST stays POST on http → https redirects; 303 correctly becomes GET. Pass follow_redirects=False (or legacy allow_redirects=False) to return the redirect response.

Async

AsyncHammock mirrors Hammock but all verbs are coroutines:

from hammock import AsyncHammock
import httpx

# URL building is sync
api = AsyncHammock("http://localhost:8000")
print(api.users("foo").posts)  # http://localhost:8000/users/foo/posts

# Execution is async
resp = await api.users.foo.GET()
resp = await api.users("foo").posts.POST(json={"x": 1})

# Custom async client
client = httpx.AsyncClient(headers={"X": "y"})
api = AsyncHammock("http://example.com", client=client)

# Context manager closes the client
async with AsyncHammock("http://example.com") as api:
    resp = await api.foo.GET()

All Hammock features (resource URIs, append_slash, redirect handling, verb list) work for AsyncHammock.

Project layout

src/hammock/
  __init__.py       # re-exports Hammock, AsyncHammock, HammockBase, bind_method
  base.py           # HammockBase – URL chaining (_spawn, _chain, _url, __getattr__, __iter__)
  hammock.py        # Hammock(httpx.Client) + bind_method
  async_hammock.py  # AsyncHammock(httpx.AsyncClient) + _bind_async_method
  py.typed          # PEP 561

Development

uv sync                  # create .venv, install dev deps
uv run pytest -q         # 47 tests (sync + async, httpretty + mocks)
uv run ty check          # types (src layout, configured via [tool.ty.src])
uv run ruff check src/hammock
uv build                 # wheel + sdist

Dev deps: httpretty, pytest, pytest-asyncio, ty, ruff.

Contributors

  • @maraujop (Miguel Araujo)
  • @rubik (Michele Lacchia)

License

Copyright (c) 2012 Kadir Pekel.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Download files

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

Source Distribution

hammock_httpx-0.3.1.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

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

hammock_httpx-0.3.1-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file hammock_httpx-0.3.1.tar.gz.

File metadata

  • Download URL: hammock_httpx-0.3.1.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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 hammock_httpx-0.3.1.tar.gz
Algorithm Hash digest
SHA256 42777d657c5bbaae09d26da94b4a3e902ba696ca19de4dd76bcd2a80d777396c
MD5 44b902d08df8745e86b4d3ce3e5ad11d
BLAKE2b-256 74064ac05d9a22be31e362bda62ecd433e833f76294791c46f42012e4902ade4

See more details on using hashes here.

File details

Details for the file hammock_httpx-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: hammock_httpx-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 11.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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 hammock_httpx-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d9518d9e8d6a3c797f3c1eb4d4981aa4216565b8c82d050fc5e0452ed6089a52
MD5 8bf2d739b7a32849076a63578baec8bb
BLAKE2b-256 f6ac2d21926c83d75c0b490b41a458a9c25fae5c8c389c14492da629d8646b9f

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 files

0.3.0

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