Expression-oriented programming for Python 3.11+ with guard clause decorators and implicit returns. Eliminate defensive coding clutter through declarative input validation and Ruby/Rust-style automatic return transformation, all while maintaining single return point architecture and zero runtime dependencies.
Project description
Parent: 📚 Documentation Hub | 📖 API Reference | ⚙️ How It Works
Finally. Expression-Oriented Programming for Python.
Stop writing defensive spaghetti. Start writing beautiful, contract-driven code.
Tired of functions where 80% of the code is validation boilerplate? Frustrated by nested conditionals checking types, values, and state before you can even think about business logic? Wish Python had expression-oriented programming like Ruby, Rust, or Scala?
modgud changes everything.
The Name: Móðguðr, Guardian of the Bridge
In Norse mythology, Móðguðr ("Furious Battler") stands as guardian of Gjallarbrú—the golden-roofed bridge spanning the river Gjöll on the path to Hel. Every soul seeking passage must stop and state their name and business before crossing. She is the threshold keeper, the boundary enforcer, the one who demands identification before allowing entry to sacred ground.
This is exactly what modgud does for your functions. Just as Móðguðr guards the bridge to the underworld, our guard clauses protect your function boundaries—challenging invalid inputs before they can corrupt your logic. And yes, we're well aware that "modgud" sounds remarkably like "mod-good" or "good mods". That's not accidental. 😊
After all, what are expression-oriented guard clauses if not genuinely good modifications to Python? You're literally adding gud mods to your functions. The Norse goddess would approve: proper guards at every threshold, allowing only the worthy to pass. Your code deserves both mythological protection and some really good enhancements.
📋 Table of Contents
- 🔥️ Why Modgud - Why modgud is essential
- ⚠️ The Problem - Why defensive programming gets messy
- 💡 The Solution - How modgud changes everything
- 🔑 Features - Six compelling features
- 🌀 Quick Start - Get started in 60 seconds
- 🔑 Key Features at a Glance - What makes modgud special
- 🌍 Real-World Example - See it in action
- 📚 Documentation - Complete guides and references
- 📦 Installation - How to install
- 🤝 Contributing - Join the project
- ✨ What's New - Latest changes
🔥 Why Modgud?
Despite Python's evolution toward more functional features (pattern matching in 3.10+, better type hints, etc.), modgud fills a critical gap: making Python functions behave like expressions without changing Python's syntax. This is uniquely valuable because:
1. Zero-Syntax-Cost Expression Orientation
While Python 3.10+ adds pattern matching, it's still statement-based. modgud's @guarded_expression transforms regular functions into expression-oriented code today, working seamlessly with Python 3.6+ codebases. You get Haskell-like guards and implicit returns without waiting for PEP acceptance or version upgrades.
2. Gradual Adoption Path
Unlike heavy functional libraries (PyMonad, toolz, etc.) that require wholesale architectural changes, modgud decorators can be applied selectively. Start with one critical function, see the benefits, expand gradually. This pragmatic approach reduces resistance in teams unfamiliar with FP.
3. Pythonic Integration
Rather than forcing Haskell or Scala idioms into Python, modgud enhances Python's existing decorator pattern. This feels natural to Python developers while delivering expression-oriented benefits. No new operators, no category theory, just cleaner functions.
4. Performance Without Overhead
Many FP libraries introduce abstraction penalties through wrapper objects and indirection. modgud's approach (presumably) operates at the function level with minimal runtime overhead, making it suitable for performance-sensitive code where other FP solutions might be rejected.
5. Compatibility Story
Working from Python 3.6+ means modgud supports the vast majority of production Python deployments. Enterprises stuck on older versions for stability can still adopt modern expression-oriented patterns without infrastructure changes.
⚠️ The Problem
Look familiar?
def process_payment(user_id, amount, payment_method):
# Validation hell begins...
if user_id is None:
return {"error": "User ID required"}
if not isinstance(user_id, int):
return {"error": "Invalid user ID type"}
if user_id <= 0:
return {"error": "User ID must be positive"}
if amount is None or amount <= 0:
return {"error": "Invalid amount"}
if amount > 10000:
return {"error": "Amount exceeds limit"}
if not payment_method or payment_method == "":
return {"error": "Payment method required"}
if payment_method not in ["card", "bank", "crypto"]:
return {"error": "Invalid payment method"}
# Finally, the actual business logic (if you can find it)
transaction = create_transaction(user_id, amount, payment_method)
return {"success": True, "transaction_id": transaction.id}
8 validation checks. 16 lines of defensive code. 2 lines of actual business logic.
Multiple return points everywhere. Business logic buried at the bottom. Error handling inconsistent. This is what happens when validation and logic mix.
💡 The Solution
modgud gives you guard clauses and expression-oriented programming in Python:
from modgud import guarded_expression, implicit_return, positive, type_check, in_range, not_empty, GuardRegistry
@guarded_expression(
positive("user_id"),
type_check(int, "user_id"),
positive("amount"),
in_range(1, 10000, "amount"),
not_empty("payment_method"),
lambda pm: pm in ["card", "bank", "crypto"] or "Invalid payment method",
implicit_return=True, # Unified approach - fully supported!
on_error={"success": False, "error": "Validation failed"}
)
def process_payment(user_id, amount, payment_method):
transaction = create_transaction(user_id, amount, payment_method)
{"success": True, "transaction_id": transaction.id} # Implicit return!
Same functionality. Zero defensive clutter. Single return point. Business logic front and center.
🔑 Features
1. 🛡️ Guard Clauses That Actually Work
Declare your function's contract upfront. Validations execute before your function runs. Guards fail fast. No more deeply nested if statements cluttering your business logic.
Before:
def withdraw(account_id, amount):
if account_id is None:
raise ValueError("Account required")
if amount <= 0:
raise ValueError("Amount must be positive")
if amount > get_balance(account_id):
raise ValueError("Insufficient funds")
# Actual work hidden at the bottom
balance = get_balance(account_id) - amount
update_balance(account_id, balance)
return balance
After:
from modgud import guarded_expression, not_none, positive
@guarded_expression(
not_none("account_id"),
positive("amount"),
lambda account_id, amount: amount <= get_balance(account_id) or "Insufficient funds",
implicit_return=True # Unified approach - clean and simple!
)
def withdraw(account_id, amount):
balance = get_balance(account_id) - amount
update_balance(account_id, balance)
balance # Clean implicit return
2. 🎨 Expression-Oriented Programming (Finally!)
The last expression in each branch is your return value. Just like Ruby, Rust, Scala, and every other modern language. No more cluttered return statements everywhere.
Before:
def classify_user(age, premium):
if age < 18:
return "minor"
elif premium:
return "premium_adult"
else:
return "standard_adult"
After:
from modgud import guarded_expression, positive
# Using the unified parameter approach
@guarded_expression(
positive("age"),
implicit_return=True
)
def classify_user(age, premium):
if age < 18:
"minor"
elif premium:
"premium_adult"
else:
"standard_adult"
Clean. Readable. Expressive. The way code should be.
3. 🎖️ Single Return Point Architecture
Every function has exactly one logical exit point. Easier debugging. Clearer control flow. No hunting through nested conditionals for hidden return statements.
Guards handle early exits. Your function handles business logic. Separation of concerns at its finest.
4. 🧩 Pre-Built Guards
Stop writing the same validations over and over:
from modgud import guarded_expression, not_none, matches_pattern, positive, in_range, not_empty, type_check
@guarded_expression(
not_none("email"),
matches_pattern(r'^[\w\.-]+@[\w\.-]+\.\w+$', "email"),
positive("age"),
in_range(13, 120, "age"),
not_empty("username"),
type_check(str, "username"),
implicit_return=True # Clean, all-in-one configuration
)
def register_user(email, age, username):
user_id = create_user_record(email, age, username)
send_welcome_email(email)
{"success": True, "user_id": user_id}
Built-in guards for:
not_none- Ensure values existnot_empty- Validate collections/strings have contentpositive- Numeric validationin_range- Bounded value checkingtype_check- Runtime type validationmatches_pattern- Regex pattern matching
Plus you can write custom guards in seconds.
Creating Custom Guards
Register your own reusable guards:
from modgud import GuardRegistry, guarded_expression
# Define a custom guard factory
def valid_age(min_age=0, max_age=150, param_name="age"):
def check(*args, **kwargs):
age = kwargs.get(param_name, args[0] if args else None)
if not isinstance(age, int):
return f"{param_name} must be an integer"
if min_age <= age <= max_age:
return True
return f"{param_name} must be between {min_age} and {max_age}"
return check
# Register it for reuse
GuardRegistry.register("valid_age", valid_age)
# Use your custom guard with implicit returns
@guarded_expression(
GuardRegistry.get("valid_age")(min_age=18, max_age=120),
implicit_return=True # All configured in one place
)
def can_vote(age):
age >= 18
5. 🎛️ Flexible Error Handling
Your code, your rules. Choose how guards fail:
Return custom values:
from modgud import guarded_expression, positive
@guarded_expression(
positive("amount"),
implicit_return=True,
on_error={"error": "Invalid amount", "code": 400}
)
def process(amount):
{"success": True, "amount": amount}
Raise exceptions:
from modgud import guarded_expression, not_empty
@guarded_expression(
not_empty("username"),
implicit_return=True,
on_error=ValueError
)
def create_account(username):
Account(username)
Custom handlers:
from modgud import guarded_expression
def audit_and_return(error_msg, *args, **kwargs):
log_security_event(error_msg)
return None
@guarded_expression(
lambda api_key: validate_key(api_key) or "Invalid key",
implicit_return=True,
on_error=audit_and_return
)
def sensitive_operation(api_key):
perform_operation()
6. 📦 Zero Dependencies
Built entirely on Python's standard library. No bloat. No version conflicts. Just clean, fast Python.
7. ✅ Battle-Tested & Type-Safe
- Full mypy type checking support
- Comprehensive test suite with 92% coverage
- Clean architecture with separation of concerns
- Thread-safe decorators
- Preserves all function metadata for debugging
🌀 Quick Start
📦 Installation
pip install modgud
Requirements: Python 3.11+
Your First Guarded Function
from modgud import guarded_expression, positive, in_range
# Option 1: Unified approach (recommended for simple cases)
@guarded_expression(
positive("x"),
in_range(1, 100, "x"),
implicit_return=True # All-in-one configuration
)
def calculate_discount(x):
if x >= 50:
x * 0.2
else:
x * 0.1
# Option 2: Separate decorators (for complex compositions)
from modgud import implicit_return
@guarded_expression(
positive("x"),
in_range(1, 100, "x")
)
@implicit_return
def calculate_discount_v2(x):
if x >= 50:
x * 0.2
else:
x * 0.1
print(calculate_discount(75)) # Returns 15.0
print(calculate_discount(25)) # Returns 2.5
print(calculate_discount(-10)) # Raises GuardClauseError
That's it. You're writing cleaner Python.
🔑 Key Features at a Glance
- 🎨 Implicit Returns by Default - Last expression in each branch is auto-returned (like Ruby/Rust/Scala)
- 🛡️ Guard Clause Decorators - Validate inputs before function execution
- 🎯 Single Return Point - One logical exit point per function
- 🧩 Pre-Built Guards - Standard validations ready to use (not_none, positive, in_range, type_check, etc.)
- 📝 Custom Guard Registry - Create and register reusable validation guards
- 🎛️ Configurable Failure Behaviors - Return values, raise exceptions, or call custom handlers
- 🏛️ Clean Architecture - Per-feature decomposition with ports + adapters
- 📦 Zero Dependencies - Uses only Python standard library
- ✅ Type-Safe - Full mypy support with proper type hints
- 🔒 Thread-Safe - No shared mutable state
- 🧪 Well-Tested - 92% coverage with 40+ comprehensive tests
🌍 Real-World Example: API Endpoint
Before modgud:
def create_user(email, age, username, password):
# Validation nightmare
if not email or email == "":
return {"status": 400, "error": "Email required"}
if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):
return {"status": 400, "error": "Invalid email format"}
if age is None or not isinstance(age, int):
return {"status": 400, "error": "Invalid age"}
if age < 13 or age > 120:
return {"status": 400, "error": "Age must be 13-120"}
if not username or username == "":
return {"status": 400, "error": "Username required"}
if len(username) < 3 or len(username) > 20:
return {"status": 400, "error": "Username must be 3-20 chars"}
if not password or password == "":
return {"status": 400, "error": "Password required"}
# Finally, the actual work
user_id = db.create_user(email, age, username, hash_password(password))
return {"status": 200, "user_id": user_id}
With modgud:
from modgud import guarded_expression, not_empty, matches_pattern, type_check, in_range
@guarded_expression(
not_empty("email"),
matches_pattern(r'^[\w\.-]+@[\w\.-]+\.\w+$', "email"),
type_check(int, "age"),
in_range(13, 120, "age"),
not_empty("username"),
lambda username: 3 <= len(username) <= 20 or "Username must be 3-20 chars",
not_empty("password"),
implicit_return=True, # Clean configuration in one place
on_error={"status": 400, "error": "Validation failed"}
)
def create_user(email, age, username, password):
user_id = db.create_user(email, age, username, hash_password(password))
{"status": 200, "user_id": user_id}
14 lines → 8 lines. Zero defensive clutter. 100% focus on business logic.
What Developers Say
"I can't believe this isn't built into Python. Guard clauses should be standard."
"Finally! Expression-oriented programming in Python. My functions are half the size now."
"The single return point architecture makes debugging so much easier."
"Pre-built guards saved me from writing the same validations 100 times."
The Name: Why Móðguðr?
In Norse mythology, Móðguðr ("Furious Battler") is the guardian of Gjallarbrú, the golden-roofed bridge spanning the river Gjöll on the path to Hel. She stands at this critical boundary, demanding that all who would cross must first state their name and business.
Just like this legendary guardian, modgud stands watch at your function boundaries—and yes, we know it also sounds like "good mods" for your Python code! 😊 After all, what better way to enhance your functions than with some good mods that guard them like a Norse goddess?
This ancient guardian embodies exactly what this library does:
- Guards the Boundary: Just as Móðguðr guards the bridge to the underworld, our guards protect function boundaries
- Demands Identification: Móðguðr requires travelers to identify themselves; our guards require parameters to validate themselves
- Controls Passage: Only the worthy may cross her bridge; only valid inputs enter your functions
- Maintains Order: She prevents chaos at the boundary; we prevent bad data from corrupting your logic
Your functions are sacred spaces. They deserve a guardian at the gate—and perhaps some good mods to make them even better!
Expression-Oriented Programming: Finally in Python
Most modern languages have embraced expression-oriented programming:
- Ruby: Everything is an expression
- Rust: if, match, and blocks are expressions
- Scala: Unified expression model
- Kotlin: When expressions, if expressions
- Even JavaScript: Arrow functions with implicit returns
Python has been left behind—until now.
What is Expression-Oriented Programming?
In expression-oriented languages, nearly every construct yields a value. This isn't just syntactic sugar—it's a fundamental paradigm shift that leads to:
- Cleaner code: No unnecessary intermediate variables
- Better composability: Everything can be chained and combined
- Functional thinking: Focus on transformations, not procedures
- Less boilerplate: The language works with you, not against you
Why It Matters
Expression-oriented programming isn't about being clever—it's about writing code that expresses intent clearly:
# Statement-oriented (traditional Python)
def get_status(user):
if user.is_active:
if user.is_premium:
status = "premium_active"
else:
status = "standard_active"
else:
status = "inactive"
return status
# Expression-oriented (with modgud)
from modgud import guarded_expression
@guarded_expression(implicit_return=True)
def get_status(user):
if user.is_active:
"premium_active" if user.is_premium else "standard_active"
else:
"inactive"
The second version isn't just shorter—it's clearer. The code structure mirrors the logic structure. No hunting for return statements. No temporary variables. Just pure intent.
Bringing Expressions to Python
With modgud, Python developers can finally write in an expression-oriented style:
- Implicit Returns: The last expression in each branch becomes the return value
- Single Return Point: One logical exit, multiple paths to get there
- Clean Composition: Guards handle preconditions, your code handles logic
# Complex business logic, expression style
from modgud import guarded_expression, not_none, positive
@guarded_expression(
not_none("order"),
positive("discount_rate"),
implicit_return=True # Expression-oriented with guards!
)
def calculate_final_price(order, discount_rate, is_premium):
base_price = order.total
if is_premium:
if base_price > 100:
base_price * (1 - discount_rate * 1.5) # Premium + bulk discount
else:
base_price * (1 - discount_rate * 1.2) # Premium discount only
else:
if base_price > 100:
base_price * (1 - discount_rate) # Standard bulk discount
else:
base_price # No discount
Every branch yields a value. No return keywords cluttering the logic. The code reads like a mathematical expression, not a procedure.
📚 Documentation
Ready to dive deeper?
- ⚙️ How It Works - Deep dive into AST transformation and the magic behind implicit returns
- 📖 API Reference - Complete API documentation for all decorators and guards
- 📚 Full Documentation Hub - Usage examples, migration guide, and advanced patterns
- 🏛️ Architecture - Clean architecture design principles
- GitHub Repository - Source code, issues, contributions
- PyPI Package - Official releases
Philosophy
Like Móðguðr ("Furious Battler"), the bridge guardian of Norse mythology who demands souls state "their name and business" before crossing to the underworld, modgud stands guard at your function boundaries—ensuring only valid inputs pass through while maintaining clean, predictable code flow.
Your functions should focus on what they do, not on validating what they receive. Guards handle the boundary. Your code handles the logic.
Single return point. Single responsibility. Single source of truth.
🎯 Usage Patterns: Two Equally Valid Approaches
modgud supports two patterns for combining guards with implicit returns. Choose based on your needs:
Pattern 1: Unified Parameter (Simple, All-in-One)
from modgud import guarded_expression, positive, not_none
# Everything configured in one decorator
@guarded_expression(
not_none("x"),
positive("x"),
implicit_return=True # Fully supported!
)
def process(x):
result = x * 2
result # Clean implicit return
Best for:
- Simple functions with guards and implicit returns
- When you want all configuration in one place
- Teaching/learning scenarios
- Functions where the transformation is core to the function's identity
Pattern 2: Separate Decorators (Flexible, Composable)
from modgud import guarded_expression, implicit_return, positive, not_none
# Separate concerns with multiple decorators
@guarded_expression(not_none("x"), positive("x"))
@implicit_return
def process(x):
result = x * 2
result # Clean implicit return
Best for:
- Complex decorator compositions
- When implicit return is optional/conditional
- Reusable decorator stacks
- Maximum flexibility
⚠️ Composition Order Warning
When using separate decorators, order matters:
# ✅ CORRECT: Guards before implicit
@guarded_expression(positive("x"))
@implicit_return
def correct(x):
x * 2
# ❌ PROBLEMATIC: Implicit before guards (may bypass guards!)
@implicit_return
@guarded_expression(positive("x")) # Guards may be bypassed!
def problematic(x):
x * 2
Individual Use Cases
# Guards only (traditional explicit returns)
@guarded_expression(
not_none("x"),
positive("x"),
implicit_return=False # Explicit returns
)
def calculate(x):
return x * 2
# Expression-orientation only (no guards)
@implicit_return
def classify(status):
if status == "active":
"user_active"
else:
"user_inactive"
Both patterns are first-class citizens in modgud. Choose the one that best fits your use case!
📦 Installation
# Using pip
pip install modgud
# Using Poetry
poetry add modgud
Requirements: Python 3.11+
🤝 Contributing
Contributions welcome! This is a young project with huge potential.
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Add your changes with tests
- Run the test suite:
poetry run pytest - Submit a pull request
See CONTRIBUTING.md for detailed guidelines.
License
MIT License - see LICENSE file for details.
✨ What's New in v1.1
🏛️ Architecture Improvements
- BREAKING: Removed all deprecated module-level registry functions
- Old:
register_guard(),get_guard(),list_custom_guards(), etc. - New: Use
GuardRegistry.register(),GuardRegistry.get(),GuardRegistry.list_guards(), etc.
- Old:
- NEW:
GuardRegistrynow uses proper singleton pattern with@classmethodinterface - NEW:
CommonGuards._register_to_global_registry()- encapsulated registration logic - IMPROVED: Better code organization - all functionality properly encapsulated in classes
- IMPROVED: Test coverage increased from 94% to 96%
Migration Guide
# Old way (v0.2.x - REMOVED)
from modgud import register_guard, get_guard
register_guard('my_guard', factory_fn)
guard = get_guard('my_guard')
# New way (v1.1+)
from modgud import GuardRegistry
GuardRegistry.register('my_guard', factory_fn)
guard = GuardRegistry.get('my_guard')
Stop writing defensive code. Start writing declarative contracts.
Welcome to modgud. Welcome to cleaner Python.
Like Móðguðr at the bridge to Hel, modgud ensures only worthy inputs pass through to your functions' sacred inner workings.
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 modgud-1.3.0.tar.gz.
File metadata
- Download URL: modgud-1.3.0.tar.gz
- Upload date:
- Size: 30.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.14.4 Darwin/25.5.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a0a7ac751e30d1f991f73c5c8a98537505208ae92d6dd7a49442e6f6554362f
|
|
| MD5 |
43fa5f0409d76e96edaa9d8959ca7c9a
|
|
| BLAKE2b-256 |
9f23e8c619ed1cf22a9558ae8bde096b0f84c6920fcdd40d4d8db7e9e88d2e1b
|
File details
Details for the file modgud-1.3.0-py3-none-any.whl.
File metadata
- Download URL: modgud-1.3.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.14.4 Darwin/25.5.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34ace3d9805f8b5135e339e807f85d78296759259c916966d51b5fdf8b359a83
|
|
| MD5 |
76c9b56cb3f1407c877a56f83d786ed2
|
|
| BLAKE2b-256 |
61cfd420a37fe6e5cbe5008a5422b65307e54b9ee9c064d03305bbe9181151a3
|