DEV Community

Ayat Saadat
Ayat Saadat

Posted on

ayat saadati — Complete Guide

ayat-toolkit: Your Go-To Swiss Army Knife for Modern Python Development

Hey there, fellow developers! I'm Ayat Saadati, and if you're anything like me, you've probably found yourself writing the same boilerplate code over and over again for common development tasks. Whether it's wrangling environment variables, adding a simple retry mechanism to an API call, or just quickly validating incoming data, these little chores can really bog down your workflow.

That's precisely why I started hacking together ayat-toolkit. It's a collection of practical, opinionated utilities designed to cut through that noise and let you focus on the actual problems you're trying to solve. Think of it as that trusty multi-tool you keep in your backpack – maybe you don't use every single bit every day, but when you need one, it's an absolute lifesaver.

My philosophy here is simple: provide robust, easy-to-use tools that solve common pain points without getting in your way. I've built this from my own experiences dealing with various projects, from small Flask apps to larger FastAPI microservices, and I genuinely believe these utilities will make your life a whole lot easier.

Features

ayat-toolkit isn't trying to reinvent the wheel, but rather make it spin smoother. Here are some of the key functionalities you'll find:

  • Robust Configuration Management: Effortlessly load environment variables from .env files and manage application settings with sensible defaults and type casting. No more os.getenv() spaghetti!
  • Handy API Utilities: Decorators for common API interaction patterns like retries for flaky network requests, designed to make your external service calls more resilient.
  • Streamlined Data Validation & Serialization: A simple, decorator-based approach to define and validate data schemas, perfect for API payloads, configuration objects, or internal data structures, leveraging the power of Python's type hints.
  • And more to come! I'm constantly adding utilities as I encounter new common problems.

Installation

Getting ayat-toolkit up and running is as straightforward as it gets. If you've got Python (3.8+) and pip, you're practically there.

pip install ayat-toolkit
Enter fullscreen mode Exit fullscreen mode

That's it! You're ready to start cutting down on boilerplate.

Basic Usage

Let's dive into some examples to show you how ayat-toolkit can simplify your code.

1. Configuration Management (ayat_toolkit.config)

Managing environment variables is a common headache, especially across different environments. ayat-toolkit makes it a breeze to load .env files and retrieve settings with sane defaults and type coercion.

First, create a .env file in your project root:

# .env
DATABASE_URL=postgresql://user:pass@host:port/db_name
API_KEY=super_secret_api_key_123
DEBUG_MODE=true
MAX_RETRIES=5
Enter fullscreen mode Exit fullscreen mode

Now, in your Python code:

# app.py
from ayat_toolkit.config import load_env, get_setting

# Always call load_env() early in your application's lifecycle.
# It defaults to loading '.env' from the current working directory.
load_env()

# --- Accessing Settings ---

# Get a required string setting
db_url = get_setting("DATABASE_URL", required=True)
print(f"Database URL: {db_url}")

# Get an optional string setting with a default value
api_key = get_setting("API_KEY", default="default_key")
print(f"API Key: {api_key}")

# Get a boolean setting (automatically converts "true"/"false" strings)
debug_mode = get_setting("DEBUG_MODE", default=False, type=bool)
print(f"Debug Mode: {debug_mode}")

# Get an integer setting
max_retries = get_setting("MAX_RETRIES", default=3, type=int)
print(f"Max Retries: {max_retries}")

# Trying to get a non-existent, required setting will raise an error
try:
    non_existent = get_setting("NON_EXISTENT_VAR", required=True)
except ValueError as e:
    print(f"Error accessing non-existent required var (expected): {e}")

# You can also load from a specific file path
# load_env(env_path="/path/to/my/other/.env")
Enter fullscreen mode Exit fullscreen mode

This module helps keep your sensitive configuration out of your codebase and makes switching environments a non-issue. I personally use this in every project now; it's just so much cleaner.

2. Handy API Utilities (ayat_toolkit.api_utils)

Dealing with external APIs often means dealing with transient network issues or rate limits. The retry decorator is your friend here, making your API calls more resilient with minimal fuss.

# api_service.py
import requests
import time
from ayat_toolkit.api_utils import retry

# Let's simulate a flaky API call
call_count = 0

@retry(attempts=3, delay=2, backoff=2) # Try 3 times, wait 2s, then 4s (2*2), then 8s (4*2)
def fetch_flaky_data(url: str):
    global call_count
    call_count += 1
    print(f"Attempt #{call_count}: Fetching data from {url}...")
    if call_count < 3: # Simulate failure for the first 2 calls
        print("  --> Simulating failure...")
        raise requests.exceptions.RequestException("Simulated network error")

    print("  --> Success!")
    response = requests.get(url, timeout=5)
    response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
    return response.json()

@retry(attempts=2, delay=0.5, catch_exceptions=(requests.exceptions.Timeout,))
def fetch_timeout_data(url: str):
    print(f"Fetching data from {url} with specific exception handling...")
    # This URL simulates a timeout
    response = requests.get(url, timeout=0.1) 
    response.raise_for_status()
    return response.json()

if __name__ == "__main__":
    print("--- Testing Flaky Data Fetch ---")
    try:
        data = fetch_flaky_data("http://httpbin.org/json")
        print("\nData fetched successfully (flaky):", data['slideshow']['author'])
    except requests.exceptions.RequestException as e:
        print(f"\nFailed after multiple retries (flaky): {e}")
    finally:
        call_count = 0 # Reset for next test

    print("\n--- Testing Timeout Data Fetch ---")
    try:
        # This will retry on Timeout, but eventually fail if the timeout is too short
        data = fetch_timeout_data("http://httpbin.org/delay/0.5") 
        print("\nData fetched successfully (timeout):", data)
    except requests.exceptions.Timeout as e:
        print(f"\nFailed after multiple retries (timeout, expected): {e}")
    except requests.exceptions.RequestException as e:
        print(f"\nFailed with generic RequestException (timeout, unexpected): {e}")

    print("\n--- Testing Successful Call ---")
    try:
        data = fetch_flaky_data("http://httpbin.org/json") # This should succeed on first try now
        print("\nData fetched successfully (direct):", data['slideshow']['author'])
    except requests.exceptions.RequestException as e:
        print(f"\nUnexpected error for successful call: {e}")
Enter fullscreen mode Exit fullscreen mode

The retry decorator is a game-changer for microservice architectures or interacting with unreliable third-party APIs. Trust me, it saves a lot of headaches and ugly try-except blocks.

3. Streamlined Data Validation & Serialization (ayat_toolkit.validation)

Defining and validating data schemas is crucial for robust applications. ayat-toolkit offers a simple decorator that transforms a standard Python class (with type hints) into a Pydantic-like validator.


python
# data_models.py
from ayat_toolkit.validation import validate_schema
from typing import List, Dict, Optional
from datetime import datetime

# Define a simple User schema
@validate_schema
class User:
    id: int
    name: str
    email: str
    is_active: bool = True # Default value
    tags: List[str] = []
    created_at: datetime

# Define a more complex Product schema with optional fields and nested data
@validate_schema
class Product:
    sku: str
    name: str
    price: float
    description: Optional[str] = None # Optional field
    dimensions: Dict[str, float] = {} # Nested dictionary
    categories: List[str] = []

if __name__ == "__main__":
    print("--- User Validation ---")
    # Valid User data
    user_data_1 = {
        "id": 101,
        "name": "Alice Wonderland",
        "email": "alice@example.com",
        "created_at": "2023-01-15T10:30:00Z" # datetime strings are automatically parsed
    }
    user_1 = User(**user_data_1)
    print(f"Valid User 1: {user_1.name}, Active: {user_1.is_active}, Created: {user_1.created_at}")

    # Another valid User with tags
    user_data_2 = {
        "id": 102,
        "name": "Bob The Builder",
        "email": "bob@example.com",
        "is_active": False,
        "tags": ["engineer", "contractor"],
        "created_at": datetime.now().isoformat()
    }
    user_2 = User(**user_data_2)
    print(f"Valid User 2: {user_2.name}, Active: {user_2.is_active}, Tags: {user_2.tags}")

    # Invalid User data (missing required field 'name')
    invalid_user_data = {
        "id": 103,
        "email": "charlie@example.com",
        "created_at": "2023-02-01T14:00:00Z"
    }
    try:
        User(**invalid_user_data)
    except ValueError as e:
        print(f"Error validating invalid user data (expected): {e}")

    print("\n--- Product Validation ---")
    # Valid Product data
    product_data_1 = {
        "sku": "LP-X100",
        "name": "Super Laptop",
        "price": 1299.99,
        "description": "A powerful laptop for professionals.",
        "dimensions": {"width": 30.5, "height
Enter fullscreen mode Exit fullscreen mode

Top comments (0)