Metadata-Version: 2.4
Name: lightreq
Version: 0.1.0
Summary: Lightweight, hackable HTTP client with adapter switching and middleware
Author-email: Ryan Febriansyah <ryanfebriansyah72@gmail.com>
License: BSD
Keywords: http,client,requests,httpx,middleware,testing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: BSD License
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: requests
Requires-Dist: requests>=2.25.0; extra == "requests"
Provides-Extra: httpx
Requires-Dist: httpx>=0.24.0; extra == "httpx"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# lightreq

![pipeline](https://gitlab.com/ryaneatfood/lightreq/badges/master/pipeline.svg) ![test coverage](https://gitlab.com/ryaneatfood/lightreq/badges/master/coverage.svg)

`lightreq` is a lightweight, extensible and composable HTTP client for Python built around the chain-of-responsibility pattern. It provides a simple unified interface over different HTTP backend adapters (like `urllib` and `requests`) and features a middleware layer architecture to easily inject logic before or after making HTTP requests

## Features

- **Unified Interface**. Use the same clean API regardless of the underlying HTTP engine
- **Adapters**. Comes with built-in adapters for `urllib` (no external dependencies) and `requests` (with connection pooling)
- **Middleware Architecture**. Easily compose behaviors like retries and logging mechanism during your HTTP requests
- **Built-in Middlewares**. Includes basic out-of-the-box `logging` and `retry` middlewares
- **Helpful Response Object**. Unified `Response` object provide easy access to status codes, headers, plain text, and JSON body

## Installation

To install `lightreq` itself, you can use pip:

```bash
pip install lightreq
```

You can use the built-in `urllib` adapter without any external dependencies, but if you want to use the `requests` adapter (highly recommended for production for connection pooling), you will need to install the `requests` library:

```bash
pip install requests
```

## Quick Start

```python
from src.client import HttpClient
from src.middleware import logging, retry

# init client using the 'requests' adapter and applying basic middlewares
client = HttpClient(
    adapter="requests",
    middleware=[logging(), retry(retries=3)]
)

# performed a GET request
response = client.get(url="https://jsonplaceholder.typicode.com/todos/1")

# extract HTTP properties from the unified Response object
print(response.is_ok())       # True
print(response.status_code)   # 200
print(response.json())        # {'userId': 1, 'id': 1, 'title': 'delectus aut autem', ...}
print(response.elapsed)       # 0.123 (time taken in seconds)
print(response.body)          # b'{\n  "userId": 1, ...}' (in raw bytes)

# close active adapter connections
client.close()
```

## Architecture

### Adapters

Adapters possibly considered as the core engine of the `lightreq`. They define how exactly an HTTP request is dispatched, and it's consisted of:

- **UrllibAdapter** (`"urllib"`): Ideal for environments where external dependencies are strictly prohibited
- **RequestsAdapter** (`"requests"`): Ideal for more complex applications requiring connection pooling or advanced session persistence

At the moment, it only supported 2 adapters, but it's designed to be extensible to support more adapters in the future such as `aiohttp` and `httpx`. Stay tuned!

### Middleware

Middlewares act as wrappers around your core request handler. They are executed in a chain, which lets them intercept and modify the `Request` object before being sent, or intercept the `Response` object before returning it to the client

You can create custom middleware using closures like, such as:

```python
def my_custom_middleware(next_handler):
    def handler(request):
        # Do something before the request is sent
        print("Sending request to:", request.url)
        
        # Pass to the next handler/middleware
        response = next_handler(request)
        
        # Do something with the response
        print("Received status code:", response.status_code)
        
        return response
    return handler
```

**Caution**: The order of middleware matters. They are executed in the order they are defined, and it's important to ensure that they are properly chained together. For example, if you have a retry middleware, it should be placed before any logging middleware to ensure that retries are logged correctly. Keep in mind that the execution flow will:

```
logging middleware -> retry middleware -> adapter (actual HTTP request)
```

## API Reference

### `HttpClient`

The core client interface of the `lightreq`. Compromised of:

- `__init__(adapter="urllib", middleware=None, **config)`: Initializes the client.
- `get(url, **kwargs)`, `post(url, **kwargs)`, `put(url, **kwargs)`, `delete(url, **kwargs)`: Convenience methods for common HTTP verbs. But somehow, it only supported 4 common HTTP methods
- `close()`: Cleans up the adapter (for example closes connection pools)

### `Response`

A custom object providing standard methods for interacting with HTTP responses, compromised of:

- `status_code`: (`int`) The HTTP status code
- `headers`: (`dict`) Dictionary of HTTP response headers
- `body`: (`bytes`) Raw response body
- `elapsed`: (`float`) The time taken to complete the request
- `text()`: Returns the string decoded response body
- `json()`: Parses the response body as JSON
- `is_ok()`: Checks if the status is within the `2xx` range
- `raise_for_status()`: Throws an Exception if the status code indicates an error (`4xx` or `5xx`)

## Background

I built this library because I wanted to implement an HTTP client from scratch with zero external dependencies. Along the way, it evolved into an extensible architecture that can be managed with other libraries (like my favorite, `requests`). Ultimately, this project serves as an *educational exercise* for the sake of *unlearning* how we manage HTTP requests and responses
