saadati-utils: A Developer's Companion for Elegant and Efficient Python Code
It's funny how often we find ourselves writing the same little helper functions, the same boilerplate, the same validation logic across projects. You know the drill – you copy-paste, tweak a bit, and move on. But then you look back and realize you've accumulated a mountain of slightly different, slightly inconsistent code. That's exactly the kind of friction saadati-utils aims to eliminate.
This isn't just another generic utility library. saadati-utils is a curated collection of opinionated, productivity-enhancing tools for Python developers, deeply inspired by the principles of clean code, maintainability, and developer ergonomics that Ayat Saadati champions in her work and discussions (you can find some of her insightful perspectives on her dev.to profile). It's about empowering you to write code that's not just functional, but a joy to read and maintain.
My personal take? When I first started seeing some of the patterns Ayat advocates, especially around clear error handling and sensible defaults, it really clicked for me. This toolkit tries to embody that spirit. It's about making the "right" way of doing things also the "easy" way.
Features
saadati-utils is designed to be lightweight but impactful, offering solutions for common development headaches:
- Robust Data Validation: Say goodbye to endless
if/elsechains for input checks. Get clear, actionable error messages out of the box. - Enhanced Logging: Beyond Python's standard
loggingmodule, this provides opinionated, structured logging helpers that just make sense for modern applications. - API Response Standardization: For those building web APIs, it offers simple ways to ensure consistent JSON responses, error formats, and pagination structures.
- Convenient Time Utilities: Dealing with
datetimeobjects can be a pain. These helpers make common operations (like parsing, formatting, or calculating durations) a breeze. - Configuration Management: Simple, intuitive ways to load and manage application configurations from various sources, prioritizing clarity and security.
Installation
Getting saadati-utils up and running is as straightforward as you'd expect from a modern Python package.
First, I always recommend using a virtual environment. It keeps your project dependencies isolated and prevents conflicts – a practice Ayat often emphasizes for good reason!
# Create a virtual environment (if you haven't already)
python3 -m venv .venv
# Activate the virtual environment
# On macOS/Linux:
source .venv/bin/activate
# On Windows (Cmd):
.venv\Scripts\activate.bat
# On Windows (PowerShell):
.venv\Scripts\Activate.ps1
Once your virtual environment is active, you can install saadati-utils directly from PyPI:
pip install saadati-utils
That's it! You're ready to start incorporating some elegance into your codebase.
Usage
Let's dive into some practical examples to show you how saadati-utils can streamline your development workflow. We'll touch on a few key modules.
Data Validation
Tired of KeyError or unexpected data types? The validation module is your friend.
from saadati_utils.validation import validate_dict, ValidationError, Field
def process_user_data(user_data: dict) -> dict:
"""
Validates and processes user data.
"""
schema = {
"user_id": Field(int, required=True, min_value=1),
"username": Field(str, required=True, min_length=3, max_length=50),
"email": Field(str, required=True, pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"),
"age": Field(int, required=False, min_value=0, max_value=120, default=18),
"is_active": Field(bool, required=False, default=True)
}
try:
validated_data = validate_dict(user_data, schema)
print("User data is valid:", validated_data)
# Further processing with guaranteed valid data
return validated_data
except ValidationError as e:
print("Validation failed:", e.errors)
raise # Re-raise or handle as appropriate for your application
# --- Example Usage ---
valid_user = {
"user_id": 123,
"username": "john.doe",
"email": "john.doe@example.com",
"is_active": True
}
invalid_user_missing_email = {
"user_id": 456,
"username": "jane"
}
invalid_user_bad_age = {
"user_id": 789,
"username": "peter.pan",
"email": "peter@neverland.com",
"age": 150
}
# This will print valid data and return it
process_user_data(valid_user)
# This will raise a ValidationError and print the specific error
try:
process_user_data(invalid_user_missing_email)
except ValidationError:
pass # Handled above, just suppressing re-raise for example
# This will also raise a ValidationError
try:
process_user_data(invalid_user_bad_age)
except ValidationError:
pass
Enhanced Logging
The loggers module provides a more structured and developer-friendly approach to logging, especially for applications that need clear context.
from saadati_utils.loggers import get_logger, StructuredLogger
# Get a default structured logger
app_logger: StructuredLogger = get_logger("MyApp")
app_logger.info("Application started successfully.",
user_id="admin",
event="startup",
version="1.0.0")
try:
result = 10 / 0
except ZeroDivisionError as e:
app_logger.error("Failed to perform calculation.",
error_type=type(e).__name__,
message=str(e),
component="math_service",
attempt=3)
app_logger.debug("Database query took too long.",
query="SELECT * FROM users",
duration_ms=2500,
threshold_ms=1000)
# You can also configure it for different outputs or levels
# For example, to log to a file:
file_logger = get_logger("FileLogger", log_file="app.log", level="DEBUG")
file_logger.warning("This message goes to both console and app.log", tag="critical_path")
The output for the above would be something like (formatted for readability, actual output might be JSON or a highly structured string depending on configuration):
[MyApp] INFO - Application started successfully. {"user_id": "admin", "event": "startup", "version": "1.0.0"}
[MyApp] ERROR - Failed to perform calculation. {"error_type": "ZeroDivisionError", "message": "division by zero", "component": "math_service", "attempt": 3}
[MyApp] DEBUG - Database query took too long. {"query": "SELECT * FROM users", "duration_ms": 2500, "threshold_ms": 1000}
[FileLogger] WARNING - This message goes to both console and app.log {"tag": "critical_path"}
API Response Standardization
For those building REST APIs, consistency is key. The api_helpers module helps you craft uniform responses.
from saadati_utils.api_helpers import ApiResponse, PaginatedResponse, ApiError
# --- Successful Response ---
data = {"id": 1, "name": "Alice", "status": "active"}
success_response = ApiResponse.success(data=data, message="User fetched successfully.")
print("Success Response:", success_response.json())
# Expected: {"status": "success", "message": "User fetched successfully.", "data": {"id": 1, "name": "Alice", "status": "active"}}
# --- Paginated Response ---
items = [{"item_id": 1}, {"item_id": 2}, {"item_id": 3}]
paginated_response = PaginatedResponse.success(
items=items,
total=100,
page=1,
page_size=10,
message="Items retrieved."
)
print("Paginated Response:", paginated_response.json())
# Expected: {"status": "success", "message": "Items retrieved.", "data": [{"item_id": 1}, ...], "pagination": {"total": 100, "page": 1, "page_size": 10}}
# --- Error Response ---
error = ApiError(code="USER_NOT_FOUND", message="The requested user could not be found.", status_code=404)
error_response = ApiResponse.error(error=error)
print("Error Response:", error_response.json())
# Expected: {"status": "error", "message": "The requested user could not be found.", "code": "USER_NOT_FOUND"}
# With multiple errors
validation_errors = [
ApiError(code="INVALID_FIELD", message="Email format is incorrect", field="email"),
ApiError(code="MISSING_FIELD", message="Username is required", field="username")
]
multi_error_response = ApiResponse.error(errors=validation_errors, message="Validation failed.")
print("Multi-Error Response:", multi_error_response.json())
# Expected: {"status": "error", "message": "Validation failed.", "errors": [{"code": "INVALID_FIELD", ...}, {"code": "MISSING_FIELD", ...}]}
Configuration Management
Managing application settings can often lead to messy code and hard-to-debug issues. saadati-utils offers a sane approach to loading configurations, prioritizing environment variables and sensible defaults.
from saadati_utils.config import ConfigManager, ConfigError
# Define your configuration schema with defaults and types
class AppConfig(ConfigManager):
APP_NAME: str = "MyCoolApp"
DEBUG_MODE: bool = False
DATABASE_URL: str
API_KEY: str = Field(str, secret=True) # Mark as secret to prevent accidental logging
# Initialize the config manager.
# It will automatically look for .env files and environment variables.
# You can also pass a file path directly.
try:
config = AppConfig()
print(f"Application Name: {config.APP_NAME}")
print(f"Debug Mode: {config.DEBUG_MODE}")
print(f"Database URL: {config.DATABASE_URL}")
# Accessing a secret field will return its value,
# but printing the config object itself won't reveal it.
print(f"API Key (secret): {config.API_KEY}")
# Demonstrate that `DATABASE_URL` is required if not set in env or .env
except ConfigError as e:
print(f"Configuration Error: {e}")
print("Please ensure DATABASE_URL and API_KEY are set in your environment or .env file.")
# To test this, create a `.env` file in your project root:
# DATABASE_URL="postgresql://user:pass@host:port/db"
# API_KEY="super-secret-key-123"
# DEBUG_MODE=True
# Or set environment variables:
# export DATABASE_URL="..."
# export API_KEY="..."
FAQ
What problem does saadati-utils solve?
It solves the problem of boilerplate fatigue, inconsistent code patterns, and the "reinventing the wheel" syndrome that plagues many development teams. By providing a set of opinionated utilities, it encourages best practices and reduces cognitive load, allowing developers to focus on unique business logic rather than generic infrastructure.
Who is this toolkit for?
Anyone building Python applications, especially those who value clean, maintainable, and robust code. If you find yourself repeatedly writing validation logic, struggling with consistent logging, or trying to standardize API responses, saadati-utils is designed for you. It's particularly useful for small to medium-sized teams looking to establish consistent coding patterns without heavy framework adoption.
Top comments (0)