Add preconditions and postconditions to Python functions with a simple decorator and safe AST-based rule evaluation.
Project description
pyensure
Lightweight Design by Contract for Python with preconditions, postconditions, async support, and AST-powered validation.
pyensure helps you define runtime contracts for Python functions using a clean decorator-based API.
Instead of scattering validation logic throughout your codebase, define expectations declaratively:
from pyensure import ensure
@ensure(
pre=("amount > 0",)
)
def withdraw(amount):
return amount
Why pyensure?
As applications grow, validation logic often becomes mixed with business logic:
def withdraw(user, amount):
if amount <= 0:
raise ValueError("Amount must be positive")
if user.balance < amount:
raise ValueError("Insufficient balance")
# business logic
With pyensure:
@ensure(
pre=(
"amount > 0",
"user.balance >= amount"
)
)
def withdraw(user, amount):
# business logic
...
This keeps contracts separate from implementation and makes intent immediately visible.
Features
Preconditions
Validate function inputs before execution.
from pyensure import ensure
@ensure(
pre=("x > 0",)
)
def add_one(x):
return x + 1
Postconditions
Validate results after execution.
@ensure(
post=("result > 0",)
)
def generate_score():
return 10
The function result is automatically exposed as:
result
inside post-condition expressions.
Attribute Access
Access object attributes directly.
@ensure(
pre=("user.age >= 18",)
)
def register(user):
return True
Function Calls
Use helper functions directly inside rules.
def is_even(x):
return x % 2 == 0
@ensure(
pre=("is_even(user_id)",)
)
def process(user_id):
return user_id
Functions defined in the decorated function's module are automatically available.
No registration required.
Method Calls
Use instance methods inside rules.
class User:
def get_age(self):
return 25
@ensure(
pre=("user.get_age() >= 18",)
)
def register(user):
return True
Boolean Expressions
AND
@ensure(
pre=("x > 0 and y > 0",)
)
def add(x, y):
return x + y
OR
@ensure(
pre=("user.is_admin or user.age >= 18",)
)
def access(user):
return True
Async Support
pyensure works with both synchronous and asynchronous functions.
@ensure(
pre=("user.age >= 18",)
)
async def create_user(user):
return user
user = User()
await create_user(user)
Runtime Contract Control
Disable all contract checks globally when needed.
from pyensure import disable, enable
disable()
# contracts are skipped
withdraw(-100)
enable()
Useful for:
- benchmarking
- performance testing
- temporary contract suspension
AST-Based Rule Evaluation
Rules are parsed using Python's Abstract Syntax Tree (AST).
Benefits:
- Controlled rule evaluation
- Clear validation boundaries
- Custom error handling
- Extensible architecture
- No direct use of
eval()
Rule Caching
Parsed AST trees are cached internally.
RULE_CACHE[rule]
Benefits:
- Faster repeated evaluations
- Avoids reparsing the same rule
- Lower runtime overhead
Type Hints
The library is fully typed.
This improves:
- IDE autocomplete
- static analysis
- maintainability
- developer experience
Custom Exceptions
All contract-related failures raise:
from pyensure import EnsureError
Example:
try:
withdraw(-100)
except EnsureError as e:
print(e)
Installation
pip install pyensure
Quick Start
from pyensure import ensure
@ensure(
pre=("x > 0",),
post=("result > x",)
)
def add_one(x):
return x + 1
print(add_one(5))
Output:
6
Supported Syntax
Comparisons
x > 0
x < 0
x >= 0
x <= 0
x == 0
x != 0
Attributes
user.age
user.balance
Function Calls
is_even(x)
validate_email(email)
Method Calls
user.get_age()
user.is_active()
Boolean Operators
x > 0 and y > 0
x > 0 or y > 0
Constants
10
"hello"
True
False
Limitations
Chained Comparisons Are Not Supported
The following is NOT supported:
1 < x < 10
Instead write:
x > 1 and x < 10
Unsupported Python Syntax
The following are intentionally unsupported:
[x for x in items]
lambda x: x + 1
{x: y}
and other complex Python constructs.
pyensure focuses on runtime contracts rather than executing arbitrary Python code.
Example: Business Rule Validation
class User:
def __init__(self, balance):
self.balance = balance
@ensure(
pre=(
"amount > 0",
"user.balance >= amount"
),
post=("result == True",)
)
def withdraw(user, amount):
user.balance -= amount
return True
Testing
The library includes automated tests covering:
- Preconditions
- Postconditions
- Attributes
- Function calls
- Method calls
- Boolean operators
- Async support
- Global enable/disable
- AST evaluation
- Error handling
Design Goals
- Simple API
- Minimal dependencies
- Readable contracts
- Runtime validation
- Async-friendly
- Production-ready
- Easy integration into existing codebases
License
MIT License
Roadmap
Future improvements may include:
- Enhanced error introspection
- Rich contract failure diagnostics
- Additional AST node support
- Advanced configuration options
- Contract reporting and debugging tools
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 pyensure_core-1.0.0.tar.gz.
File metadata
- Download URL: pyensure_core-1.0.0.tar.gz
- Upload date:
- Size: 8.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
934f47155041d1eb55a822f8478b32c23d9ecb267a5426df784da2e0dc70cc27
|
|
| MD5 |
6cc75987a9e5c880bf417823ead9bb1c
|
|
| BLAKE2b-256 |
92d68a8bb9e892e0c7f8632d5557dc40c04cc2bfd83c44dd9ed874e2913ab377
|
File details
Details for the file pyensure_core-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pyensure_core-1.0.0-py3-none-any.whl
- Upload date:
- Size: 7.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
137ed3bed7cfc0e29e46a6b15787db39d73cacacf998972f519809e9e46f96d7
|
|
| MD5 |
ab698215ba3ad90234ac81252c9e1f13
|
|
| BLAKE2b-256 |
23d4f482c29b7e202fb378ad5f40141ee95b75a6211ce5b1869329a15450c7a1
|