AndreaCensi / contracts

PyContracts is a Python package that allows to declare constraints on function parameters and return values. Contracts can be specified using Python3 annotations, or inside a docstring. PyContracts supports a basic type system, variables binding, arithmetic constraints, and has several specialized contracts and an extension API.
http://andreacensi.github.io/contracts/
Other
398 stars 62 forks source link

Different behaviour when using 2 contract notations #66

Open jcrmatos opened 6 years ago

jcrmatos commented 6 years ago

Hello,

I'm just trying out pycontracts and found this strange behavior when using different contract notations. I import my module and run the function with an invalid argument and the contract's checks work fine when using this notation:

square_size = 9  # type: int

nr_rows = square_size  # type: int
nr_cols = square_size  # type: int

@contract(row_nr='int, >= 0, < $nr_rows', col_nr='int, >= 0, < $nr_cols',
                  returns='int,>= 0, < $square_size')
def loc2square_nr(row_nr, col_nr):
    # type: (int, int) -> int
    """Convert location (row, column) to square number.

    (int, int) -> int

    :param row_nr: Row number to convert to square number.
    :param col_nr: Column number to convert to square number.
    :return: Square number.
    """
    square_nr = (int(row_nr / square_side) * square_side
                 + int(col_nr / square_side))  # type: int

    return square_nr

But if I change the contract notation to inside the docstring, like this:

square_size = 9  # type: int

nr_rows = square_size  # type: int
nr_cols = square_size  # type: int

@contract
def loc2square_nr(row_nr, col_nr):
    # type: (int, int) -> int
    """Convert location (row, column) to square number.

    :param row_nr: Row number to convert to square number.
    :type row_nr: int, >= 0, < $nr_rows
    :param col_nr: Column number to convert to square number.
    :type col_nr: int, >= 0, < $nr_cols
    :return: Square number.
    :rtype: int, >= 0, < $square_size
    """
    square_nr = (int(row_nr / square_side) * square_side
                 + int(col_nr / square_side))  # type: int

    return square_nr

Python returns a huge error when importing the module (I called it base.py). This is the final part of the error:

    value = self._parseNoCache(instring, loc, doActions, callPreParse)
  File "C:\Users\JMatos\Envs\solve_sudoku\lib\site-packages\pyparsing.py", line
1405, in _parseNoCache
    tokens = fn( instring, tokensStart, retTokens )
  File "C:\Users\JMatos\Envs\solve_sudoku\lib\site-packages\pyparsing.py", line
1049, in wrapper
    ret = func(*args[limit[0]:])
  File "C:\Users\JMatos\Envs\solve_sudoku\lib\site-packages\contracts\utils.py",
 line 247, in f2
    return f(*args, **kwargs)
  File "C:\Users\JMatos\Envs\solve_sudoku\lib\site-packages\contracts\library\sc
oped_variables.py", line 95, in scoped_parse_action
    val = _lookup_from_calling_scope(tokens[0])
  File "C:\Users\JMatos\Envs\solve_sudoku\lib\site-packages\contracts\library\sc
oped_variables.py", line 67, in _lookup_from_calling_scope
    raise ExternalScopedVariableNotFound(token)
contracts.interface.ExternalScopedVariableNotFound: Token not found: 'square_siz
e'.
>>>

If I understand correctly, pycontracts doesn't recognize the variable square_size in the 2nd notation. But it does on the 1st.

I'm using Python 32b 3.5.4 with pycontracts 1.8.3. I'm running on a Windows 7 Pro x64 PC.

Thanks,

JM

AndreaCensi commented 6 years ago

Thanks for the report @jcrmatos - this is a bug.