Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyensure_core-1.0.0.tar.gz (8.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyensure_core-1.0.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

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

Hashes for pyensure_core-1.0.0.tar.gz
Algorithm Hash digest
SHA256 934f47155041d1eb55a822f8478b32c23d9ecb267a5426df784da2e0dc70cc27
MD5 6cc75987a9e5c880bf417823ead9bb1c
BLAKE2b-256 92d68a8bb9e892e0c7f8632d5557dc40c04cc2bfd83c44dd9ed874e2913ab377

See more details on using hashes here.

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

Hashes for pyensure_core-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 137ed3bed7cfc0e29e46a6b15787db39d73cacacf998972f519809e9e46f96d7
MD5 ab698215ba3ad90234ac81252c9e1f13
BLAKE2b-256 23d4f482c29b7e202fb378ad5f40141ee95b75a6211ce5b1869329a15450c7a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page