Skip to content

Commit

Permalink
Merge pull request #10 from orsinium/global-state
Browse files Browse the repository at this point in the history
Global state
  • Loading branch information
orsinium committed Jul 6, 2019
2 parents 27cda7c + c5b7469 commit 6a7cd7b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 15 deletions.
9 changes: 5 additions & 4 deletions deal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
__title__ = 'deal'
__version__ = '2.3.0'
__author__ = 'Gram Orsinium'
__license__ = 'LGPL 3.0'
__license__ = 'MIT'


# version synonym
VERSION = __version__


from .exceptions import * # noQA
from .aliases import * # noQA
from .schemes import Scheme # noQA
from .aliases import * # noQA
from .exceptions import * # noQA
from .schemes import Scheme # noQA
from .state import reset, switch # noQA
25 changes: 14 additions & 11 deletions deal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@

from . import exceptions
from .schemes import is_scheme


__all__ = ['Pre', 'Post', 'Invariant', 'Raises']
from .state import state


class _Base:
exception = exceptions.ContractError

def __init__(self, validator, message: str = None,
def __init__(self, validator, *, message: str = None,
exception: Type[Exception] = None, debug: bool = False):
"""
Step 1. Set contract (validator).
Expand Down Expand Up @@ -64,19 +62,24 @@ def validate(self, *args, **kwargs) -> None:
# is invalid (falsy result)
raise self.exception

@property
def enabled(self) -> bool:
if self.debug:
return state.debug
else:
return state.main

def __call__(self, function: Callable) -> Callable:
"""
Step 2. Return wrapped function.
"""
# if contract only for dev, but this is prod, do not wrap function
if self.debug and not __debug__:
return function # pragma: no cover

self.function = function

# we can't use partial here because python can't bound class instance to partial.
def wrapped(*args, **kwargs):
return self.patched_function(*args, **kwargs)
if self.enabled:
return self.patched_function(*args, **kwargs)
else:
return function(*args, **kwargs)

return update_wrapper(wrapped, function)

Expand Down Expand Up @@ -231,7 +234,7 @@ def patched_function(self, *args, **kwargs):
class Offline(_Base):
exception = exceptions.OfflineContractError

def __init__(self, message=None, exception=None, debug=False):
def __init__(self, *, message=None, exception=None, debug=False):
"""
Step 1. Init params.
"""
Expand Down
20 changes: 20 additions & 0 deletions deal/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@


class _State:
def __init__(self):
self.reset()

def reset(self) -> None:
self.main = True
self.debug = __debug__

def switch(self, *, main: bool = None, debug: bool = None) -> None:
if main is not None:
self.main = main
if debug is not None:
self.debug = debug


state = _State()
reset = state.reset
switch = state.switch
20 changes: 20 additions & 0 deletions docs/disable.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,23 @@ If you run Python with `-O` option, contracts will be disabled. This is uses Pyt

> The built-in variable `__debug__` is True under normal circumstances, False when optimization is requested (command line option -O).
> - [Official documentation](https://docs.python.org/3/reference/simple_stmts.html#assert)
Also, you can explicitly enable or disable contracts:

```python
# disable contracts without `debug=True`
deal.switch(main=False)

# enable contracts with `debug=True`
deal.switch(debug=True)

# disable contracts with `debug=True`
deal.switch(debug=False)

# disable all contracts
deal.switch(main=False, debug=False)

# return default behavior
# (`main` enabled, `debug` the same as `__debug__`)
deal.reset()
```
28 changes: 28 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,5 +423,33 @@ def func(msg, do):
func(False, True)


class StateTest(unittest.TestCase):
def setUp(self):
deal.reset()

def tearDown(self):
deal.reset()

def test_debug(self):
func = deal.pre(lambda x: x > 0, debug=True)(lambda x: x * 2)
deal.switch(debug=False)
with self.subTest(text='good'):
func(-2)
deal.switch(debug=True)
with self.subTest(text='error'):
with self.assertRaises(deal.PreContractError):
func(-2)

def test_main(self):
func = deal.pre(lambda x: x > 0)(lambda x: x * 2)
deal.switch(main=False)
with self.subTest(text='good'):
func(-2)
deal.switch(main=True)
with self.subTest(text='error'):
with self.assertRaises(deal.PreContractError):
func(-2)


if __name__ == '__main__':
unittest.main()

0 comments on commit 6a7cd7b

Please sign in to comment.