DEV Community

Ayat Saadat
Ayat Saadat

Posted on

ayat saadati — Complete Guide

SaadatiLib: A Developer's Essential Toolkit

Hey there! If you're anything like me, you've probably found yourself writing the same utility functions over and over again across different projects. Things like robust API call wrappers, sensible data serialization, or even just a pre-configured logger that doesn't make you tear your hair out. That's exactly why I started building SaadatiLib – it's my personal collection of battle-tested Python utilities, packaged up for anyone who wants to stop reinventing the wheel and get straight to building cool stuff.

My philosophy here is simple: provide tools that are easy to use, opinionated enough to save you time, but flexible enough not to get in your way. I've poured years of practical experience into these modules, aiming to make common development tasks just... easier.


🚀 Features

SaadatiLib isn't trying to be a massive framework; it's a toolbox. Here's what you'll find inside:

  • Robust API Client (saadatilib.api_client): A simple, yet powerful wrapper around requests with built-in retry mechanisms, timeout handling, and smart header management. Perfect for interacting with external APIs without the boilerplate.
  • Data Serialization & Manipulation (saadatilib.data_utils): Helpers for handling JSON, CSV, and other common data formats. Think quick-and-dirty parsing, validation, and transformation functions that actually work.
  • Configurable Logging (saadatilib.logger): A pre-configured logging setup that's ready to go. You get sensible defaults for console output and file logging, easily customizable to your project's needs.
  • CLI Argument Helpers (saadatilib.cli_utils): Sometimes you just need a quick command-line script. These utilities simplify parsing common arguments and options, making your scripts more user-friendly.

🛠️ Installation

Getting SaadatiLib up and running is as straightforward as you'd expect from a Python package.

Prerequisites

You'll need Python 3.7+ installed. I've been building and testing with recent versions, so anything in that range should be perfectly fine.

Using pip

The easiest way to get SaadatiLib is via pip:

pip install saadatilib
Enter fullscreen mode Exit fullscreen mode

If you're working in a virtual environment (and you absolutely should be!), activate it first:

python -m venv .venv
source .venv/bin/activate  # On Windows, use `.venv\Scripts\activate`
pip install saadatilib
Enter fullscreen mode Exit fullscreen mode

From Source (for contributors or cutting-edge)

If you're interested in contributing or just want the absolute latest changes that haven't hit pip yet, you can install from the source repository.

  1. Clone the repository:

    git clone https://github.com/ayat_saadat/saadatilib.git # (Fictional URL, replace with actual if exists)
    cd saadatilib
    
  2. Install in editable mode:

    pip install -e .
    

    This allows you to make changes to the source code and see them reflected immediately without needing to reinstall.


📚 Usage

Let's dive into some practical examples to show you how to put SaadatiLib to work.

1. Robust API Calls

The api_client module is a real time-saver. No more writing try...except blocks for every network call or dealing with HTTP status codes manually.

from saadatilib.api_client import APIClient
from saadatilib.logger import get_logger

# Initialize a logger for this script
logger = get_logger(__name__)

# Create an API client instance
# You can pass base_url, default_headers, and retry configurations
client = APIClient(
    base_url="https://api.example.com",
    default_headers={"X-API-Key": "your_secret_key"},
    retries=3,          # Retry up to 3 times on transient errors
    backoff_factor=0.5, # Exponential backoff for retries
    timeout=5           # 5-second timeout for each request
)

def fetch_user_data(user_id: str):
    try:
        # Make a GET request
        response = client.get(f"/users/{user_id}")

        # The client automatically raises an exception for bad status codes (4xx/5xx)
        # unless `raise_for_status=False` is passed.
        # It also returns the parsed JSON by default if content-type is application/json.
        user_data = response.json()
        logger.info(f"Successfully fetched data for user {user_id}: {user_data['name']}")
        return user_data

    except client.APIError as e:
        logger.error(f"Failed to fetch user {user_id}: {e}")
        # Here, `e.response` would give you the underlying requests.Response object
        if e.response:
            logger.error(f"Response status: {e.response.status_code}, content: {e.response.text}")
        return None
    except Exception as e:
        logger.critical(f"An unexpected error occurred: {e}")
        return None

# Example usage
user_1_data = fetch_user_data("abc-123")
user_2_data = fetch_user_data("def-456")

# Example of a POST request
def create_new_item(item_payload: dict):
    try:
        response = client.post("/items", json=item_payload)
        new_item = response.json()
        logger.info(f"Created new item: {new_item['id']}")
        return new_item
    except client.APIError as e:
        logger.error(f"Failed to create item: {e}")
        return None

# create_new_item({"name": "Widget X", "price": 99.99})
Enter fullscreen mode Exit fullscreen mode

2. Data Utilities

The data_utils module helps with common data format tasks.

from saadatilib.data_utils import read_json_file, write_json_file, flatten_dict, validate_data_schema
import json

# Let's imagine we have a complex nested dictionary
nested_data = {
    "id": "123",
    "details": {
        "name": "Product A",
        "specs": {
            "weight": "10kg",
            "dimensions": "1x2x3"
        }
    },
    "tags": ["electronics", "heavy"]
}

# Flatten a dictionary
flat_data = flatten_dict(nested_data)
print("Flattened Data:")
print(json.dumps(flat_data, indent=2))
# Expected output:
# {
#   "id": "123",
#   "details_name": "Product A",
#   "details_specs_weight": "10kg",
#   "details_specs_dimensions": "1x2x3",
#   "tags": ["electronics", "heavy"]
# }

# Example of reading/writing JSON files
sample_file = "temp_data.json"
sample_data = {"key": "value", "list": [1, 2, 3]}

write_json_file(sample_file, sample_data, indent=4)
print(f"\nWritten data to {sample_file}")

read_data = read_json_file(sample_file)
print(f"Read data from {sample_file}: {read_data}")

# Data Schema Validation (basic example using a simple dictionary as schema)
# For more advanced validation, consider Pydantic or Marshmallow.
# This utility provides a quick check for required keys.
schema = {
    "id": str,
    "name": str,
    "price": (int, float)
}

valid_item = {"id": "item-001", "name": "Book", "price": 25.50}
invalid_item = {"id": "item-002", "name": "Pen"} # Missing price

if validate_data_schema(valid_item, schema):
    print("\nValid item passes schema check.")
else:
    print("\nValid item fails schema check.") # Should not happen

if validate_data_schema(invalid_item, schema):
    print("Invalid item passes schema check.")
else:
    print("Invalid item fails schema check (as expected).")
Enter fullscreen mode Exit fullscreen mode

3. Configurable Logging

Getting a consistent logging experience across your applications shouldn't be a chore.

from saadatilib.logger import get_logger, configure_logging
import logging

# Configure logging once at the start of your application
# This sets up console logging with INFO level by default.
# You can also specify file logging, custom formats, etc.
configure_logging(
    level=logging.DEBUG, # Set global minimum level
    log_file="application.log",
    file_level=logging.INFO,
    console_level=logging.DEBUG
)

# Get a logger instance for your module
my_module_logger = get_logger("my_application.module_a")
another_module_logger = get_logger("my_application.module_b")

my_module_logger.debug("This is a debug message from module A.")
my_module_logger.info("This is an info message from module A.")
my_module_logger.warning("Something potentially problematic happened in module A!")
my_module_logger.error("An error occurred in module A!")
my_module_logger.critical("Critical failure in module A, shutting down!")

another_module_logger.info("Hello from module B!")
Enter fullscreen mode Exit fullscreen mode

🏗️ API Reference (Key Modules)

Here's a quick rundown of the main components you'll interact with:

Module Key Classes/Functions Description
saadatilib.api_client APIClient, APIError Configurable HTTP client with retry logic and custom error handling.
saadatilib.data_utils read_json_file, write_json_file, flatten_dict, validate_data_schema Utilities for JSON I/O, dictionary manipulation, and basic schema validation.
saadatilib.logger get_logger, configure_logging Centralized logging setup and retrieval of named logger instances.
saadatilib.cli_utils parse_args_with_defaults (example) Helpers for common command-line argument parsing patterns.

⚙️ Configuration

Beyond the initial configure_logging call, many SaadatiLib components are configured at instantiation. For example, the APIClient takes parameters for base URL, headers, retries, and timeouts directly in its constructor.

I generally prefer this approach over global configuration files for individual components, as it makes your code more explicit and easier to test.

🤝 Contributing

I'm always keen to hear ideas or see contributions! If you've got a great utility function that fits the philosophy of making common tasks easier, don't hesitate.

  1. Fork the repository (if it were a real repo, this would be the first step!).
  2. Create a new branch: git checkout -b feature/your-awesome-feature
  3. Implement your changes and write tests (this is crucial!).
  4. Ensure all tests pass (pytest).
  5. Commit your changes: git commit -m "feat: Add your awesome feature"
  6. Push to your branch: git push origin feature/your-awesome-feature
  7. Open a Pull Request (again, if this were a live project!)

When contributing, please try to adhere to existing code style and add comprehensive docstrings and type hints.


❓ FAQ

Q: Is SaadatiLib production-ready?
A: Absolutely! I use these utilities in my own production systems. While it's a personal toolkit, it's built with robustness and reliability in mind.

Q: What about dependencies?
A: SaadatiLib aims to keep external dependencies to a minimum. Currently, requests is the primary external dependency for the api_client module. All dependencies are listed in setup.py (or pyproject.toml).

Q: Can I use only a part of SaadatiLib?
A: Yes, the modules are designed to be relatively independent. You can from saadatilib.api_client import APIClient without needing to import anything else from the library.

Q: Why didn't you use for ?
A: Great question! Often, it's because I found existing libraries either too heavy, too opinionated in a way that didn't fit my common use cases, or simply required more setup than I wanted for quick utility tasks. SaadatiLib aims for a lightweight, batteries-included-for-common-cases approach.


⚠️ Troubleshooting

"ModuleNotFoundError: No module named 'saadatilib'"

  • Cause: The library isn't installed or your Python environment isn't correctly configured.
  • Solution: Make sure you've run pip install saadatilib within your active virtual environment. If you're using an IDE, ensure it's configured to use the correct Python interpreter associated with your virtual environment.

saadatilib.api_client.APIError: API request failed...

  • Cause: This error indicates that an HTTP request made via APIClient returned a status code outside the 2xx range (e.g., 400, 404, 500).
  • Solution:
    • Check the error message and the response.status_code and `response

Top comments (0)