Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pytest, put tests into separate dir #70

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
graft docs
graft tests
graft boolean

include LICENSE.txt
Expand Down
69 changes: 0 additions & 69 deletions README.md

This file was deleted.

83 changes: 83 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
==========
boolean.py
==========

.. image:: https://img.shields.io/travis/bastikr/boolean.py.svg
:target: https://travis-ci.org/bastikr/boolean.py
.. image:: https://img.shields.io/pypi/wheel/boolean.py.svg
:target: https://pypi.python.org/pypi/boolean.py/
.. image:: https://img.shields.io/pypi/v/boolean.py.svg
:target: https://pypi.python.org/pypi/boolean.py/
.. image:: https://img.shields.io/pypi/pyversions/boolean.py.svg
:target: https://pypi.python.org/pypi/boolean.py/
.. image:: https://img.shields.io/badge/license-BSD-blue.svg
:target: https://raw.githubusercontent.com/bastikr/boolean.py/master/LICENSE.txt

This python package implements `Boolean algebra`_. It defines two base elements,
:code:`TRUE` and :code:`FALSE`, and a :code:`Symbol` class. Expressions are
built in terms of :code:`AND`, :code:`OR` and :code:`NOT`. Other functions, like
:code:`XOR` and :code:`NAND`, are not implemented but can be emulated with
:code:`AND` or and :code:`NOT`. Expressions are constructed from parsed strings
or in Python.

.. _`Boolean algebra`: https://en.wikipedia.org/wiki/Boolean_algebra

Example
=======

.. code-block:: python

>>> import boolean
>>> algebra = boolean.BooleanAlgebra()
>>> expression1 = algebra.parse(u'apple and (oranges or banana) and not banana', simplify=False)
>>> expression1
AND(Symbol('apple'), OR(Symbol('oranges'), Symbol('banana')), NOT(Symbol('banana')))

>>> expression2 = algebra.parse(u'(oranges | banana) and not banana & apple', simplify=True)
>>> expression2
AND(Symbol('apple'), NOT(Symbol('banana')), Symbol('oranges'))

>>> expression1 == expression2
False
>>> expression1.simplify() == expression2
True

Documentation
=============

http://readthedocs.org/docs/booleanpy/en/latest/

Installation
============

.. code-block:: shell

pip install boolean.py

Testing
=======

Test :code:`boolean.py` with your current Python environment:

.. code-block:: shell

python setup.py test

Test with all of the supported Python environments using :code:`tox`:

.. code-block:: shell

pip install -r test-requirements.txt
tox

If :code:`tox` throws :code:`InterpreterNotFound`, limit it to python
interpreters that are actually installed on your machine:

.. code-block:: shell

tox -e py27,py36

License
=======

Simplified BSD License
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
universal = 1

[aliases]
test = pytest
release = clean --all sdist --formats=gztar bdist_wheel register upload

[metadata]
Expand Down
29 changes: 6 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,23 @@
from setuptools import find_packages
from setuptools import setup


long_desc = '''
This library helps you deal with boolean expressions and algebra with variables
and the boolean functions AND, OR, NOT.

You can parse expressions from strings and simplify and compare expressions.
You can also easily create your custom algreba and mini DSL and create custom
tokenizers to handle custom expressions.

For extensive documentation look either into the docs directory or view it online, at
https://booleanpy.readthedocs.org/en/latest/

https://github.com/bastikr/boolean.py

Copyright (c) 2009-2017 Sebastian Kraemer, [email protected] and others

Released under revised BSD license.
'''

with open('README.rst') as readme:
long_description = readme.read()

setup(
name='boolean.py',
version='3.4',
license='revised BSD license',
license='Simplified BSD license',
description='Define boolean algebras, create and parse boolean expressions and create custom boolean DSL.',
long_description=long_desc,
long_description=long_description,
author='Sebastian Kraemer',
author_email='[email protected]',
url='https://github.com/bastikr/boolean.py',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
test_loader='unittest:TestLoader',
Copy link
Collaborator

@pombredanne pombredanne Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous test suite could run equally well with or without pytest. Any special reason to make py.test a hard requirement for tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to force everybody to use the same thing. The more options you have, the more (compatibility?) things you have to keep in mind. With just one thing there is only one way: pip install -r requirements.txt; pytest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I know this is a very old PR, but...)

The use of @pytest decorators makes pytest a hard requirement, full stop. (And I agree with @alisianoi: it's better not to support multiple test frameworks, which lead to everyone executing the tests in subtly different ways. Do that, you've stopped testing your code; now you're compatibility-testing the frameworks instead.)

pytest-runner is deprecated. (As are any use of test_runner, test_suite, tests_require, etc. in setup.py. setup.py as a whole is deprecated, of course, but its test args are "super double deprecated".) These days, tox would be a better choice as test runner.

test_suite='boolean.test_boolean',
setup_requires=['pytest-runner'],
tests_require=['pytest'],
keywords='boolean expression, boolean algebra, logic, expression parser',
classifiers=[
'Development Status :: 4 - Beta',
Expand Down
5 changes: 4 additions & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
tox==2.7.0
tox
pytest
pytest-cov
pytest-xdist
Empty file added tests/__init__.py
Empty file.
116 changes: 116 additions & 0 deletions tests/mock_advanced_algebra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import tokenize

try:
# Python 2
basestring
except NameError:
# Python 3
basestring = str

try:
from io import StringIO
except ImportError:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO

from boolean import BooleanAlgebra, Symbol
from boolean import TOKEN_LPAR, TOKEN_RPAR
from boolean import TOKEN_TRUE, TOKEN_FALSE
from boolean import TOKEN_AND, TOKEN_OR, TOKEN_NOT

class PlainVar(Symbol):
"Plain boolean variable"

class ColonDotVar(Symbol):
"Colon and dot-separated string boolean variable"

class AdvancedAlgebra(BooleanAlgebra):
def tokenize(self, expr):
"""
Example custom tokenizer derived from the standard Python tokenizer
with a few extra features: #-style comments are supported and a
colon- and dot-separated string is recognized and stored in custom
symbols. In contrast with the standard tokenizer, only these
boolean operators are recognized : & | ! and or not.

For more advanced tokenization you could also consider forking the
`tokenize` standard library module.
"""

if not isinstance(expr, basestring):
raise TypeError('expr must be string but it is %s.' % type(expr))

# mapping of lowercase token strings to a token object instance for
# standard operators, parens and common true or false symbols
TOKENS = {
'&': TOKEN_AND,
'and': TOKEN_AND,
'|': TOKEN_OR,
'or': TOKEN_OR,
'!': TOKEN_NOT,
'not': TOKEN_NOT,
'(': TOKEN_LPAR,
')': TOKEN_RPAR,
'true': TOKEN_TRUE,
'1': TOKEN_TRUE,
'false': TOKEN_FALSE,
'0': TOKEN_FALSE,
'none': TOKEN_FALSE,
}

ignored_token_types = (
tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT,
tokenize.INDENT, tokenize.DEDENT,
tokenize.ENDMARKER
)

# note: an unbalanced expression may raise a TokenError here.
tokens = ((toktype, tok, row, col,) for toktype, tok, (row, col,), _, _
in tokenize.generate_tokens(StringIO(expr).readline)
if tok and tok.strip())

COLON_DOT = (':', '.',)

def build_symbol(current_dotted):
if current_dotted:
if any(s in current_dotted for s in COLON_DOT):
sym = ColonDotVar(current_dotted)
else:
sym = PlainVar(current_dotted)
return sym

# accumulator for dotted symbols that span several `tokenize` tokens
dotted, srow, scol = '', None, None

for toktype, tok, row, col in tokens:
if toktype in ignored_token_types:
# we reached a break point and should yield the current dotted
symbol = build_symbol(dotted)
if symbol is not None:
yield symbol, dotted, (srow, scol)
dotted, srow, scol = '', None, None

continue

std_token = TOKENS.get(tok.lower())
if std_token is not None:
# we reached a break point and should yield the current dotted
symbol = build_symbol(dotted)
if symbol is not None:
yield symbol, dotted, (srow, scol)
dotted, srow, scol = '', 0, 0

yield std_token, tok, (row, col)

continue

if toktype == tokenize.NAME or (toktype == tokenize.OP and tok in COLON_DOT):
if not dotted:
srow = row
scol = col
dotted += tok

else:
raise TypeError('Unknown token: %(tok)r at line: %(row)r, column: %(col)r' % locals())
31 changes: 31 additions & 0 deletions tests/mock_custom_algebra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from boolean import BooleanAlgebra, Symbol

from boolean import TOKEN_SYMBOL
from boolean import TOKEN_LPAR, TOKEN_RPAR
from boolean import TOKEN_AND, TOKEN_OR, TOKEN_NOT

class CustomSymbol(Symbol):
pass

class CustomAlgebra(BooleanAlgebra):
def __init__(self, Symbol_class=CustomSymbol):
super(CustomAlgebra, self).__init__(Symbol_class=CustomSymbol)

def tokenize(self, s):
"Sample tokenizer using custom operators and symbols"
ops = {
'WHY_NOT': TOKEN_OR,
'ALSO': TOKEN_AND,
'NEITHER': TOKEN_NOT,
'(': TOKEN_LPAR,
')': TOKEN_RPAR,
}

for row, line in enumerate(s.splitlines(False)):
for col, tok in enumerate(line.split()):
if tok in ops:
yield ops[tok], tok, (row, col)
elif tok == 'Custom':
yield self.Symbol(tok), tok, (row, col)
else:
yield TOKEN_SYMBOL, tok, (row, col)
Loading