• Stars
    star
    149
  • Rank 248,619 (Top 5 %)
  • Language
    Python
  • License
    Apache License 2.0
  • Created about 11 years ago
  • Updated 12 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Validate conditions, Python style.

ensure: Literate assertions in Python

ensure is a set of simple assertion helpers that let you write more expressive, literate, concise, and readable Pythonic code for validating conditions. It's inspired by should.js, expect.js, and builds on top of the unittest/JUnit assert helpers.

If you use Python 3, you can use ensure to enforce your function signature annotations: see PEP 3107 and the @ensure_annotations decorator below.

Because ensure is fast, is a standalone library (not part of a test framework), doesn't monkey-patch anything or use DSLs, and doesn't use the assert statement (which is liable to be turned off with the -O flag), it can be used to validate conditions in production code, not just for testing (though it certainly works as a BDD test utility library).

Aside from better looking code, a big reason to use ensure is that it provides more consistent, readable, and informative error messages when things go wrong. See Motivation and Goals for more.

Installation

pip install ensure

Synopsis

from ensure import ensure

ensure(1).is_an(int)
ensure({1: {2: 3}}).equals({1: {2: 3}}).also.contains(1)
ensure({1: "a"}).has_key(1).whose_value.has_length(1)
ensure.each_of([{1: 2}, {3: 4}]).is_a(dict).of(int).to(int)
ensure(int).called_with("1100101", base=2).returns(101)
ensure(dict).called_with(1, 2).raises(TypeError)
check(1).is_a(float).or_raise(Exception, "An error happened: {msg}. See http://example.com for more information.")

In Python 3:

from ensure import ensure_annotations

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

See More examples below.

Notes

The ensure module exports the Ensure class and its convenience instance ensure. Instances of the class are callable, and the call will reset the contents that the instance is inspecting, so you can reuse it for many checks (as seen above).

The class raises EnsureError (a subclass of AssertionError) by default.

There are several ways to chain clauses, depending on the grammatical context: .also, .which, and .whose_value are available per examples below.

Raising custom exceptions

You can pass a callable or exception class as the error_factory keyword argument to Ensure(), or you can use the Check class or its convenience instance check(). This class behaves like Ensure, but does not raise errors immediately. It saves them and chains the methods otherwise(), or_raise() and or_call() to the end of the clauses.

from ensure import check

check("w00t").is_an(int).or_raise(Exception)
check(1).is_a(float).or_raise(Exception, "An error happened: {msg}. See http://example.com for more information.")
check("w00t").is_an(int).or_raise(MyException, 1, 2, x=3, y=4)
def build_fancy_exception(original_exception):
    return MyException(original_exception)

check("w00t").is_an(int).otherwise(build_fancy_exception)
check("w00t").is_an(int).or_call(build_fancy_exception, *args, **kwargs)

More examples

ensure({1: {2: 3}}).is_not_equal_to({1: {2: 4}})
ensure(True).does_not_equal(False)
ensure(1).is_in(range(10))
ensure(True).is_a(bool)
ensure(True).is_(True)
ensure(True).is_not(False)
ensure(["train", "boat"]).contains_one_of(["train"])
ensure(range(8)).contains(5)
ensure(["spam"]).contains_none_of(["eggs", "ham"])
ensure("abcdef").contains_some_of("abcxyz")
ensure("abcdef").contains_one_or_more_of("abcxyz")
ensure("abcdef").contains_all_of("acf")
ensure("abcd").contains_only("dcba")
ensure("abc").does_not_contain("xyz")
ensure([1, 2, 3]).contains_no(float)
ensure(1).is_in(range(10))
ensure("z").is_not_in("abc")
ensure(None).is_not_in([])
ensure(dict).has_attribute('__contains__').which.is_callable()
ensure({1: "a", 2: "b", 3: "c"}).has_keys([1, 2])
ensure({1: "a", 2: "b"}).has_only_keys([1, 2])
ensure(1).is_true()
ensure(0).is_false()
ensure(None).is_none()
ensure(1).is_not_none()
ensure("").is_empty()
ensure([1, 2]).is_nonempty().also.has_length(2)
ensure(1.1).is_a(float).which.equals(1.10)
ensure(KeyError()).is_an(Exception)
ensure({x: str(x) for x in range(5)}).is_a_nonempty(dict).of(int).to(str)
ensure({}).is_an_empty(dict)
ensure(None).is_not_a(list)
import re
ensure("abc").matches("A", flags=re.IGNORECASE)
ensure([1, 2, 3]).is_an_iterable_of(int)
ensure([1, 2, 3]).is_a_list_of(int)
ensure({1, 2, 3}).is_a_set_of(int)
ensure({1: 2, 3: 4}).is_a_mapping_of(int).to(int)
ensure({1: 2, 3: 4}).is_a_dict_of(int).to(int)
ensure({1: 2, 3: 4}).is_a(dict).of(int).to(int)
ensure(10**100).is_numeric()
ensure(lambda: 1).is_callable()
ensure("abc").has_length(3)
ensure("abc").has_length(min=3, max=8)
ensure(1).is_greater_than(0)
ensure(1).exceeds(0)
ensure(0).is_less_than(1)
ensure(1).is_greater_than_or_equal_to(1)
ensure(0).is_less_than_or_equal_to(0)
ensure(1).is_positive()
ensure(1.1).is_a_positive(float)
ensure(-1).is_negative()
ensure(-1).is_a_negative(int)
ensure(0).is_nonnegative()
ensure(0).is_a_nonnegative(int)
ensure([1,2,3]).is_sorted()
ensure("{x} {y}".format).called_with(x=1, y=2).equals("1 2")
ensure(int).called_with("1100101", base=2).returns(101)
ensure("{x} {y}".format).with_args(x=1, y=2).is_a(str)
with ensure().raises(ZeroDivisionError):
    1/0
with ensure().raises_regex(NameError, "'w00t' is not defined"):
    w00t

See complete API documentation.

Enforcing function annotations

Use the @ensure_annotations decorator to enforce function signature annotations:

from ensure import ensure_annotations

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

f(1, 2.3)
>>> 3.3
f(1, 2)
>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

Compare this runtime type checking to compile-time checking in Mypy and type hinting in PEP 484/Python 3.5+.

Motivation and goals

Many BDD assertion libraries suffer from an excess of magic, or end up having to construct statements that don't parse as English easily. ensure is deliberately kept simple to avoid succumbing to either issue. The source is easy to read and extend.

Work remains to make error messages raised by ensure even more readable, informative, and consistent. Going forward, ability to introspect exceptions to extract structured error information will be a major development focus. You will be in control of how much information is presented in each error, which context it's thrown from, and what introspection capabilities the exception object will have.

The original use case for ensure is as an I/O validation helper for API endpoints, where the client needs to be sent a very clear message about what went wrong, some structured information (such as an HTTP error code and machine-readable reference to a failing element) may need to be added, and some information may need to be hidden from the client. To further improve on that, we will work on better error translation, marshalling, message formatting, and schema validation helpers.

Authors

  • Andrey Kislyuk
  • Harrison Metzger

Links

Bugs

Please report bugs, issues, feature requests, etc. on GitHub.

License

Licensed under the terms of the Apache License, Version 2.0.

https://codecov.io/github/kislyuk/ensure/coverage.svg?branch=master

More Repositories

1

yq

Command-line YAML, XML, TOML processor - jq wrapper for YAML/XML/TOML documents
Python
2,587
star
2

argcomplete

Python and tab completion, better together.
Python
1,410
star
3

watchtower

Python CloudWatch Logging: Log Analytics and Application Intelligence
Python
778
star
4

keymaker

Lightweight SSH key management on AWS EC2
Python
223
star
5

aegea

Amazon Web Services Operator Interface
Python
68
star
6

eight

Python 2 to the power of 3
Python
47
star
7

rehash

Resumable hashlib: a picklable interface to CPython's OpenSSL-based hashlib standard library
Python
37
star
8

tweak

Python application configuration engine
Python
15
star
9

node-complexify

node.js port of jquery.complexify.js
JavaScript
14
star
10

likelybin

Unsupervised binning of metagenomes using MCMC.
C
4
star
11

gs

A minimalistic Google Storage client
Python
3
star
12

encryptxml

Python XML Encryption library
Python
3
star
13

hstspreload

HSTS and HPKP support for Python HTTP clients - This repository has moved to:
Makefile
3
star
14

roof

Friendly Python test coverage reporting
Python
3
star
15

katalin

A GitHub action that generates LLM-assisted suggestions for improving Python code in PRs
Python
3
star
16

ftp_fetcher

Shell
2
star
17

dx-falcon

DNAnexus Falcon app prototype
Python
2
star
18

dmtk

DNA Modification Toolkit
Python
2
star
19

tractorbeam

File I/O staging for JSON documents with Amazon S3 URLs
Python
2
star
20

hipston

High Performance Storage Object Namespace
Python
2
star
21

httpnext

Next Generation HTTP for Python
Python
2
star
22

dynamoq

A DynamoDB Command Line Interface with JSON I/O
Python
2
star
23

roslyn

Old mirror of Microsoft .NET Compiler Platform, now at:
C#
1
star
24

dnanexus-oauth2-demo

CSS
1
star
25

xbcf

eXtensible Block Compression Format
Python
1
star
26

everytab

A Chrome tab manager extension that doesn't suck
JavaScript
1
star
27

akutils

1
star
28

kislyuk.com

Personal page
Ruby
1
star
29

simpleyaml

1
star
30

aosp-de

1
star
31

thermos

Deploy Flask applications on AWS Lambda
1
star
32

dxca

DNAnexus Celera Assembler integration
Shell
1
star
33

kislyuk.github.io

HTML
1
star
34

kislyuk

1
star
35

carta-playbooks

Ansible playbooks for cartographer.us
Shell
1
star
36

cartographer

Map the soul.
Python
1
star
37

urchin

1
star