Skip to main content

PowerNap

Deployed to PyPI GitHub Repository Continuous Integration Coverage MIT License Contributor Covenant

A small REST client that recharges your batteries

PowerNap is a simplistic JSON REST API client, a small wrapper around httpx that makes your code read slightly better.

Installation

$ pip install powernap

Usage

# You will need an httpx Client. Let's do a GitHub client!
httpx_client = httpx.Client(
    base_url="https://api.github.com/v3",
    headers={"Authorization": f"token {token}"}
)

# Instantiate the PowerNap client
github_client = powernap.PowerNap(httpx_client=httpx_client)

# PowerNap will help you build complex URLs in a pythonic-looking way:
repo = github_client.repos("ewjoachim/powernap").get()

# And access the json responses like objects
count_stars = repo.stargazers_count

# You can also easily send POST requests.
github_client.repos("ewjoachim/powernap").issues(42).comments.post(
    body=f"Wow look! This repo has {count_stars} stars!"
)

Build complex URLs

You can use client(something) or client.something, and chain calls. With the form client(something), you can pass multiple parameters, and they will be joined together

# The next calls are all identical and target:
#    {base_url}/repos/ewjoachim/powernap/stargazers
github_client.repos("ewjoachim/powernap").stargazers.get()
github_client.repos("ewjoachim").powernap("stargazers").get()
github_client.repos("ewjoachim", "powernap", "stargazers").get()
github_client.repos("ewjoachim", "powernap")("stargazers").get()
github_client("repos/ewjoachim/powernap/stargazers").get()

# The recommended way is to use client.something for static parts of the url and
# client(something) with a variable for dynamic parts.

# You can also target the base url directly
# {base_url}
github_client().get()

Access the json responses like objects

On the json responses, all objects (even nested) are configured so that you can get keys with the object.key syntax in addition to the classic object["key"].

# GET /nested_json -> {"a": {"b":{"c": "d"}}}
response = some_api_client.nested_json.get()

assert response == {"a": {"b":{"c": "d"}}}
assert response["a"]["b"]["c"] == "d"
# But also the magic form:
assert response.a.b.c == "d"

Arguments

The arguments in the .get/delete() calls are used as query parameters on the call.

The arguments in the .post/put/patch() calls are put together, and passed as the json payload for the call.

Response types

If the response comes with Content-Type: application/json, then you'll get the "magic" json response as described above. Otherwise, if the content type is text/*, you'll get a string, and otherwise, you'll get bytes.

Exceptions

If you want to avoid httpx exceptions to reach your code, so as to maintain a good abstraction layer, you may want to subclass Powernap and implement handle_exception(self, exc). You'll receive an httpx.HttpError and it's your responsibility to raise whatever exception you see fit. Not raising an exception in this context is considered as an error, though.

from typing import NoReturn

class ApiError(MyProjectError):
    pass

class ForbiddenError(ApiError):
    pass

class ApiClient(PowerNap):
    def handle_exception(self, exc: httpx.HttpError) -> NoReturn:
        if exc.response.status_code == 403:
            raise ForbiddenError
        raise ApiError

More control over input and output

This magic is nice and all, but sometimes, you may want more control. If you want to send additional headers or a non-JSON-dict payload, or if you want to read the headers on the response, it possible too.

Instead of calling .get(), use either .get.i(), .get.o() or .get.io() (it works with any method: get/post/put/patch/delete):

  • If you call with .get.i(...) (or .get.io(...)), you control the input. The method keyword arguments will be passed to the underlying httpx.Client().get(...) as-is.
  • If you call with .get.o(...) (or .get.io(...)), you get the original output. The function will return a httpx.Response object. (Note that in this case, we will still have called .raise_for_status())

If you regularly use get.io(), it's probably that PowerNap is probably not the project you need. Use httpx.Client directly, build something to help you craft URLs (you can steal copy the relevant code, don't forget to copy the license too)

Name

While looking for a name for this lib, I looked at all the synonyms for "small rest". It's amazing how many other projects have gone the same route. To name a few:

  • nap looks awesome! Unrelated lib but same goals as this one. Seems unmaintained but I'm not sure a lib like this needs a lot of maintenance.
  • catnap
  • respite
  • snooze is not taken but snooze-server is and I didn't want to create confusion.

Funnily enough, a consequent number of those projects have the same goals as this one, yet don't have the exact look and feel I'm trying to achieve.

Credits where due

This lib is heavily inspired from githubpy, which is under Apache license.

Metadata

Release files for powernap 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for powernap 2.0.0
File Size Uploaded
powernap-2.0.0.tar.gz 6.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for powernap 2.0.0
File Interpreter ABI Platform
powernap-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 13.1 kB

Release files / powernap-2.0.0.tar.gz

Download URL powernap-2.0.0.tar.gz
Size 6.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1879bf74e68c2b821edf871a489322fc45c56c8caa766c421dea4a68d32abf59
BLAKE2b-256 checksum
How to use checksums
e89fa59df9ba47d48924fdf3d495be7e7ca60ad4a305674d697941a9a0b20694
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 12, 2025.

Transparency log

Release files / powernap-2.0.0-py3-none-any.whl

Download URL powernap-2.0.0-py3-none-any.whl
Size 6.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1edcd298ba70ac0dbd4e8141a4cb73d01cb468ff681d55365b50e7098d16a02a
BLAKE2b-256 checksum
How to use checksums
4884f1085e316818fb8e5fd95643b15b449eda242a8a123808c142fd2ce21f9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 12, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.0.1

2 release files

1.0.0

2 release 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