Skip to main content

A declarative Python library for building chained HTTP request workflows using a fluent interface.

Project description

ApiChainer

Tests PyPI version Python versions License: MIT

ApiChainer is a declarative Python library for building chained HTTP requests using a fluent interface. It allows you to describe complex, multi-step API workflows, passing data between requests without writing a lot of boilerplate code.

The library provides both synchronous (ApiChain) and asynchronous (AsyncApiChain) clients, built on top of requests and aiohttp respectively.

Key Features

  • Fluent Interface: Create sequences of requests with methods like .get(), .post(), .put(), .patch(), .delete().

  • Data Forwarding (Placeholders): Easily use data from a previous step's response in subsequent requests ({prev.json.id}, {prev.text}, {prev.headers}).

  • Centralized Context: Define initial data ({ctx.user_id}) that is accessible in any step.

  • Advanced Placeholders: Access specific steps with {step[0].json} or {step[1].text} syntax.

  • File Operations: Upload files with .upload_file() and save responses to files.

  • Macros: Use high-level methods for complex operations like polling (.poll()) or retrying on failure (.retry_on_failure()).

  • Synchronous & Asynchronous Modes: Full support for both traditional scripts (requests) and async frameworks like FastAPI (aiohttp).

  • Callbacks: Inject logging or monitoring logic with on_before_step, on_after_step, and on_error callbacks.

  • Granular Error Handling: Clear exceptions for debugging issues with requests, placeholders, polling timeouts, or general chain errors.

  • Security: Built-in URL validation and safe placeholder processing.

  • Logging Support: Optional logging for debugging with enable_logging=True.

Installation

Requirements: Python 3.9+

pip install apichainer

For development:

poetry install

Quick Start

Synchronous Example

Let's say we need to log in, get a token, and then use that token to request user data.

from apichainer import ApiChain, RequestError

BASE_URL = "https://api.example.com"

try:
    # 1. POST /login to get a token
    # 2. GET /me using the token from step 1
    chain = (
        ApiChain(base_url=BASE_URL)
        .post("/login", json={"username": "admin", "password": "password123"})
        .set_header("Authorization", "Bearer {prev.json.token}")
        .get("/me")
    )

    response = chain.run()
    
    print("User data:", response.json())

except RequestError as e:
    print(f"Chain execution failed: {e}")
    print(f"Status code: {e.response.status_code}")

Asynchronous Example with FastAPI

The same workflow, but in an asynchronous context.

# main.py (in your FastAPI application)
from fastapi import FastAPI
from apichainer import AsyncApiChain

app = FastAPI()
BASE_URL = "https://api.internal.service"

@app.get("/process-user/{user_id}")
async def process_user(user_id: int):
    # Context to pass static data into the chain
    context = {"user_id": user_id}
    
    # 1. Get task information for the user
    # 2. Post the result to another endpoint
    chain = (
        AsyncApiChain(base_url=BASE_URL, initial_context=context)
        .get("/tasks/{ctx.user_id}")
        .post("/results", json={
            "userId": "{ctx.user_id}",
            "taskData": "{prev.json}"
        })
    )

    response = await chain.run_async()
    
    return {"status": response.status, "response_text": await response.text()}

Advanced Features

File Upload

from apichainer import ApiChain

chain = (
    ApiChain(base_url="https://api.example.com")
    .post("/login", json={"username": "user", "password": "pass"})
    .upload_file("/files", "/path/to/file.pdf", field_name="document")
    .retry_on_failure(attempts=3, delay_seconds=2)
)

response = chain.run()

Polling Operations

from apichainer import ApiChain

def is_ready(response):
    return response.json().get("status") == "completed"

chain = (
    ApiChain(base_url="https://api.example.com")
    .post("/jobs", json={"type": "export"})
    .poll("/jobs/{prev.json.job_id}", until=is_ready, interval_seconds=5, timeout_seconds=120)
)

response = chain.run()

Advanced Placeholders

from apichainer import ApiChain

chain = (
    ApiChain(base_url="https://api.example.com", initial_context={"user_id": 123})
    .get("/users/{ctx.user_id}")  # Context placeholder
    .get("/posts/{prev.json.id}")  # Previous step JSON
    .get("/comments?status_code={prev.status_code}")  # Previous step status
    .get("/metadata?step0_text={step[0].text}")  # Specific step access
)

response = chain.run()

Callbacks and Logging

from apichainer import ApiChain

def before_step(step_data):
    print(f"Executing: {step_data}")

def after_step(result):
    print(f"Result: {result['status_code']}")

def on_error(error):
    print(f"Error occurred: {error}")

chain = (
    ApiChain(
        base_url="https://api.example.com",
        on_before_step=before_step,
        on_after_step=after_step,
        on_error=on_error,
        enable_logging=True
    )
    .get("/data")
)

response = chain.run()

Save Response to File

from apichainer import ApiChain, AsyncApiChain

# Synchronous
chain = ApiChain(base_url="https://api.example.com").get("/download")
chain.run_and_save_to_file("output.pdf")

# Asynchronous
async_chain = AsyncApiChain(base_url="https://api.example.com").get("/download")
await async_chain.run_and_save_to_file_async("output.pdf")

Exception Handling

ApiChainer provides specific exceptions for different error scenarios:

from apichainer import (
    ApiChain, 
    RequestError,      # HTTP request errors
    PlaceholderError,  # Placeholder resolution errors  
    ChainError,        # General chain execution errors
    PollingTimeoutError # Polling timeout errors
)

try:
    response = ApiChain(base_url="https://api.example.com").get("/data").run()
except RequestError as e:
    print(f"HTTP error: {e}")
    print(f"Response: {e.response}")
except PlaceholderError as e:
    print(f"Placeholder error: {e}")
except PollingTimeoutError as e:
    print(f"Polling timeout: {e}")
except ChainError as e:
    print(f"Chain error: {e}")

Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

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

Source Distribution

apichainer-0.1.0.tar.gz (11.2 kB view details)

Uploaded Source

Built Distribution

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

apichainer-0.1.0-py3-none-any.whl (9.6 kB view details)

Uploaded Python 3

File details

Details for the file apichainer-0.1.0.tar.gz.

File metadata

  • Download URL: apichainer-0.1.0.tar.gz
  • Upload date:
  • Size: 11.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.12 Linux/6.11.0-1015-azure

File hashes

Hashes for apichainer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 049c94d2561ff4df5b01e0040dcbabaa3b47772ac9acef50949344603c3123d9
MD5 7ee1c9a3d8e64761811237f1dae03677
BLAKE2b-256 04cb7bb06ddedda51976a58499c41435b0f9a657916f4b3b55d80b04f2e14afd

See more details on using hashes here.

File details

Details for the file apichainer-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: apichainer-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.12 Linux/6.11.0-1015-azure

File hashes

Hashes for apichainer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 22546fee85f5a318507685ab37b2e184a9d4f00ec5a56c316a72838f8376a18c
MD5 a17ea3fb95cac81eeb5bfd9161285335
BLAKE2b-256 fdb3113fc9b7cff8f43c3c3908665498941c3da333ed39709277b0bdc808f41a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page