A Python library for validating function arguments using decorators
Project description
py-guard
A lightweight, decorator-based Python library for validating function arguments with an intuitive and expressive syntax.
✨ Features
- 🎯 Simple decorator syntax - Just add
@guard()to your functions - 🔍 Type validation - Automatic type checking from type hints
- ✅ Choice validation - Restrict values to specific options
- 📝 String validation - Email, URL, regex patterns, and length
- 📦 Schema validation - Validate dictionary structures
- 🔧 Custom validators - Easy to create and register your own
- 🎨 Clear error messages - Detailed validation failures
- 🚀 Zero dependencies - Pure Python, stdlib only
📦 Installation
pip install pyguard
Requirements:
- Python 3.9 or higher
🚀 Quick Start
from pyguard import guard
@guard(
age={"gte": 18, "lte": 120},
email={"email": True},
username={"length": (3, 20), "regex": r"^[a-zA-Z0-9_]+$"}
)
def register_user(age: int, email: str, username: str):
return f"User {username} registered successfully!"
# Valid call
register_user(25, "john@example.com", "john_doe")
# Raises GuardValidationError: age must be at least 18
register_user(15, "john@example.com", "john_doe")
# Raises GuardValidationError: email must be a valid email address
register_user(25, "invalid-email", "john_doe")
📖 Available Validators
Comparison Validators
Validate numeric values with comparison operators:
@guard(
age={"gte": 18}, # Greater than or equal
score={"gt": 0}, # Greater than
temperature={"lte": 100}, # Less than or equal
count={"lt": 1000} # Less than
)
def process_data(age: int, score: float, temperature: int, count: int):
pass
Keywords:
gte- Greater than or equal togt- Greater thanlte- Less than or equal tolt- Less than
Type Validation
Type validation is automatic when you use type hints:
@guard()
def calculate(x: int, y: float) -> float:
return x * y
# Raises GuardValidationError: x must be of type <class 'int'>
calculate("5", 2.5)
You can also explicitly specify types:
@guard(value={"type": int})
def process(value):
pass
Choice Validation
Restrict values to a specific set of choices:
@guard(currency={"choices": ["USD", "EUR", "GBP"]})
def process_payment(amount: float, currency: str):
pass
# Works with Enums too
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
@guard(status={"choices": Status})
def update_status(status: Status):
pass
String Validators
Length Validation
@guard(
username={"length": (3, 20)}, # Min 3, max 20
password={"length": (8, None)}, # Min 8, no max
code={"length": (None, 10)} # No min, max 10
)
def create_account(username: str, password: str, code: str):
pass
Email Validation
@guard(email={"email": True})
def send_message(email: str, message: str):
pass
URL Validation
@guard(website={"url": True})
def add_bookmark(website: str):
pass
Regex Validation
@guard(
phone={"regex": r"^\d{3}-\d{3}-\d{4}$"},
zip_code={"regex": r"^\d{5}(-\d{4})?$"}
)
def update_contact(phone: str, zip_code: str):
pass
Dictionary Validators
Required Keys
@guard(config={"keys": ["host", "port", "database"]})
def connect_db(config: dict):
pass
# Must include all required keys
connect_db({"host": "localhost", "port": 5432, "database": "mydb"})
Schema Validation
@guard(
user={
"schema": {
"name": str,
"age": int,
"email": str
}
}
)
def create_user(user: dict):
pass
# Validates both keys and types
create_user({"name": "John", "age": 30, "email": "john@example.com"})
Required Validation
Mark arguments as required (cannot be None):
@guard(user_id={"required": True})
def get_user(user_id):
pass
# Raises GuardValidationError: user_id must not be None
get_user(None)
🎨 Multiple Validators
You can combine multiple validators on a single argument:
@guard(
username={
"length": (3, 20),
"regex": r"^[a-zA-Z0-9_]+$"
},
age={
"gte": 13,
"lte": 120,
"type": int
}
)
def register(username: str, age: int):
pass
🔧 Custom Validators
Create your own validators by extending the Validator class:
from pyguard import Guard, Validator, guard
from typing import Any
@Guard.register_validator("divisible_by")
class DivisibleByValidator(Validator):
def validate(self, value: Any):
if value % self.expected != 0:
return f"{self.name} must be divisible by {self.expected}"
return None
# Use your custom validator
@guard(number={"divisible_by": 5})
def process_number(number: int):
return number * 2
process_number(10) # OK
process_number(7) # Raises GuardValidationError
🎯 Real-World Examples
User Registration
@guard(
username={"length": (3, 20), "regex": r"^[a-zA-Z0-9_]+$"},
email={"email": True},
password={"length": (8, 128)},
age={"gte": 13, "lte": 120}
)
def register_user(username: str, email: str, password: str, age: int):
# Registration logic here
return {"status": "success", "username": username}
API Pagination
@guard(
page={"gte": 1, "type": int},
per_page={"gte": 1, "lte": 100, "type": int}
)
def get_items(page: int = 1, per_page: int = 20):
# Fetch paginated items
return {"page": page, "per_page": per_page, "items": []}
Configuration Validation
@guard(
config={
"schema": {
"database": dict,
"api_key": str,
"timeout": int
},
"keys": ["database", "api_key"]
}
)
def initialize_app(config: dict):
# App initialization
pass
🚨 Error Handling
When validation fails, GuardValidationError is raised with detailed information:
from pyguard import guard, GuardValidationError
@guard(age={"gte": 18}, email={"email": True})
def register(age: int, email: str):
pass
try:
register(15, "invalid-email")
except GuardValidationError as e:
print(e)
# Output:
# Validation failed for register:
# - age:
# • age must be at least 18
# - email:
# • email must be a valid email address
# Access structured error data
print(e.errors)
# {'age': ['age must be at least 18'],
# 'email': ['email must be a valid email address']}
🔄 Optional Parameters
Use Optional type hints for parameters that can be None:
from typing import Optional
@guard(
name={"length": (2, 50)},
email={"email": True}
)
def update_profile(name: str, email: Optional[str] = None):
pass
update_profile("John") # OK - email is optional
update_profile("John", "john@example.com") # OK
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
📞 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
🗺️ Roadmap
- Pydantic integration for schema validation
- Async function support
- Additional string validators (startswith, endswith, contains)
- Collection validators (all_items, any_item, unique)
- Custom error messages
- Add support for Python 3.9
📊 Changelog
See CHANGELOG.md for version history.
Made with ❤️ by Mateusz Jasinski & Tadow Dev
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyguard_args-0.1.0.tar.gz.
File metadata
- Download URL: pyguard_args-0.1.0.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46ac03a67e5ccfae539893a75658de508e388e087711a8a81329c64aeb8b74ef
|
|
| MD5 |
5b85c30f3546bdd1b2755ac318f1f9fa
|
|
| BLAKE2b-256 |
36eae5352c5b17d5e6e7503e33b43292208ae40d0528da760aedc5659893992a
|
File details
Details for the file pyguard_args-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyguard_args-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cd6eeb501d8d3e6ee9bd5e6ae3a6cf7136d304e8b34d6f0261d85f0de9f7e81
|
|
| MD5 |
b52aace4180251411e332ac246983a27
|
|
| BLAKE2b-256 |
59ba6357f310c42b1a9864e012e19fa9755929e5d9e6b34e38b48a214ffd3341
|