def with_validator_boolean_tester(t):
    from valid8 import validator
    from valid8.validation_lib import instance_of

    with validator('t', t, instance_of=tuple) as v:
        v.alid = len(t) == 2 \
                 and instance_of(t[0], Real) and (0 <= t[0] <= 1) \
                 and instance_of(t[1], str) and len(t[1]) == 3 and t[1].islower()
示例#2
0
def test_readme_index_usage_function():
    """ Tests that the examples provided in the index page under Usage examples/Function are correct """

    from mini_lambda import s, Len
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of

    @validate_arg('name',
                  instance_of(str),
                  Len(s) > 0,
                  help_msg='name should be a non-empty string')
    def build_house(name, surface=None):
        print('Building house... DONE !')
        return name, surface

    build_house('sweet home', 200)

    with pytest.raises(InputValidationError) as exc_info:
        build_house('', 200)  # name is invalid
    e = exc_info.value
    assert str(e) == "name should be a non-empty string. " \
                     "Error validating input [name=''] for function [build_house]. " \
                     "At least one validation function failed for value ''. " \
                     "Successes: [\"instance_of_%r\"] / Failures: {'len(s) > 0': 'Returned False.'}." \
                     "" % str

    from mini_lambda import s, x, l, Len
    from valid8 import validate_arg, validate_out
    from valid8.validation_lib import instance_of, is_multiple_of

    @validate_arg('name',
                  instance_of(str),
                  Len(s) > 0,
                  help_msg='name should be a non-empty string')
    @validate_arg(
        'surface', (x >= 0) & (x < 10000),
        is_multiple_of(100),
        help_msg='Surface should be a multiple of 100 between 0 and 10000.')
    @validate_out(instance_of(tuple), Len(l) == 2)
    def build_house(name, surface=None):
        print('Building house... DONE !')
        return name, surface

    build_house('sweet home')
    build_house('sweet home', None)  # No error !

    with pytest.raises(TypeError):
        is_multiple_of(100)(None)

    with pytest.raises(TypeError):
        (Len(s) > 0).evaluate(None)
def test_readme_index_combining_autoclass():
    """ Tests that the examples provided in the index page under Combining/autoclass are correct """

    from autoclass import autoclass
    from mini_lambda import s, x, Len
    from valid8 import validate_field, ClassFieldValidationError
    from valid8.validation_lib import instance_of, is_multiple_of

    class InvalidNameError(ClassFieldValidationError):
        help_msg = 'name should be a non-empty string'

    class InvalidSurfaceError(ClassFieldValidationError):
        help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'

    @validate_field('name',
                    instance_of(str),
                    Len(s) > 0,
                    error_type=InvalidNameError)
    @validate_field('surface', (x >= 0) & (x < 10000),
                    is_multiple_of(100),
                    error_type=InvalidSurfaceError)
    @autoclass
    class House(object):
        def __init__(self, name, surface=None):
            pass

    h = House('sweet home', 200)

    h.surface = None  # Valid (surface is nonable by signature)

    with pytest.raises(InvalidNameError):
        h.name = ''  # InvalidNameError

    with pytest.raises(InvalidSurfaceError):
        h.surface = 10000  # InvalidSurfaceError
def class_field_mini_lambda(value):
    from mini_lambda import InputVar, Len
    from valid8 import validate_field
    from valid8.validation_lib import instance_of
    from valid8.validation_lib.mini_lambda_ import Instance_of

    # just for fun: we create our custom mini_lambda variable named 't'
    t = InputVar('t', tuple)

    @validate_field(
        't',
        instance_of(tuple),
        Len(t) == 2,
        # the first element is a float between 0 and 1
        Instance_of(t[0], Real),
        (0 <= t[0]) & (t[0] <= 1),
        # the 2d element is a lowercase string of len 3
        Instance_of(t[1], str),
        Len(t[1]) == 3,
        t[1].islower())
    class Foo:
        def __init__(self, t):
            self.s = t

    Foo(value)
示例#5
0
    class House(object):

        @validate_arg('name', instance_of(str), Len(s) > 0,
                      error_type=InvalidNameError)
        @validate_arg('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
                      error_type=InvalidSurfaceError)
        def __init__(self, name, surface=None):
            pass
示例#6
0
def function_io_builtin(value):
    from valid8 import validate_io
    from valid8.validation_lib import instance_of, gt

    @validate_io(x=[instance_of(Integral), gt(0)])
    def my_function3(x):
        pass

    my_function3(value)
示例#7
0
def test_testing():
    """ """
    from mini_lambda import s, Len
    from valid8 import assert_valid, Validator
    from valid8.validation_lib import instance_of

    name = 'sweet_home'

    assert_valid('name',
                 name,
                 instance_of(str),
                 Len(s) > 0,
                 help_msg='name should be a non-empty string')

    v = Validator(instance_of(str),
                  Len(s) > 0,
                  help_msg='name should be a non-empty string')
    v.assert_valid('name', name)
def function_input_builtin_stdlib(value):
    from valid8 import validate_arg, and_
    from valid8.validation_lib import instance_of, has_length, on_each_, between

    @validate_arg(
        't',
        instance_of(tuple),
        has_length(2),
        on_each_(
            # the first element is a float between 0 and 1
            and_(instance_of(Real), between(0, 1)),
            # the 2d element is a lowercase string of len 3
            and_(instance_of(str), has_length(3), str.islower),
        ))
    def my_function(t):
        pass

    my_function(value)
示例#9
0
def function_input_builtin_stdlib(value):
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of, minlen

    @validate_arg('s', instance_of(str), minlen(1), str.islower)
    def my_function(s):
        pass

    my_function(value)
示例#10
0
def function_output_builtin(value):
    from valid8 import validate_out
    from valid8.validation_lib import instance_of, gt

    @validate_out(instance_of(Integral), gt(0))
    def my_function2():
        return value

    my_function2()
示例#11
0
def function_input_builtin(value):
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of, gt

    @validate_arg('x', instance_of(Integral), gt(0))
    def my_function(x):
        pass

    my_function(value)
示例#12
0
def class_field_builtin_stdlib(value):
    from valid8 import validate_field
    from valid8.validation_lib import instance_of, minlen

    @validate_field('s', instance_of(str), minlen(1), str.islower)
    class Foo:
        def __init__(self, s):
            self.s = s

    Foo(value)
示例#13
0
def function_input_mini_lambda(value):
    from mini_lambda import s, Len
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of

    @validate_arg('s', instance_of(str), Len(s) > 0, s.islower())
    def my_function(s):
        pass

    my_function(value)
示例#14
0
def class_field_builtin_stdlib(value):
    from valid8 import validate_field
    from valid8.validation_lib import instance_of, gt

    @validate_field('x', instance_of(Integral), gt(0))
    class Foo:
        def __init__(self, x):
            self.x = x

    Foo(value)
示例#15
0
def function_io_mini_lambda(value):
    from mini_lambda import x
    from valid8 import validate_io
    from valid8.validation_lib import instance_of

    @validate_io(x=[instance_of(Integral), x >= 0])
    def my_function3(x):
        pass

    my_function3(value)
示例#16
0
def function_output_mini_lambda(value):
    from mini_lambda import x
    from valid8 import validate_out
    from valid8.validation_lib import instance_of

    @validate_out(instance_of(Integral), x >= 0)
    def my_function2():
        return value

    my_function2()
示例#17
0
def function_input_mini_lambda(value):
    from mini_lambda import x
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of

    @validate_arg('x', instance_of(Integral), x >= 0)
    def my_function(x):
        pass

    my_function(value)
示例#18
0
def class_field_builtin_stdlib(value):
    from valid8 import validate_field, and_
    from valid8.validation_lib import instance_of, has_length, on_each_, between

    @validate_field(
        't',
        instance_of(tuple),
        has_length(2),
        on_each_(
            # the first element is a float between 0 and 1
            and_(instance_of(Real), between(0, 1)),
            # the 2d element is a lowercase string of len 3
            and_(instance_of(str), has_length(3), str.islower),
        ))
    class Foo:
        def __init__(self, t):
            self.s = t

    Foo(value)
示例#19
0
def class_field_mini_lambda(value):
    from mini_lambda import x
    from valid8 import validate_field
    from valid8.validation_lib import instance_of

    @validate_field('x', instance_of(Integral), x >= 0)
    class Foo:
        def __init__(self, x):
            self.x = x

    Foo(value)
    def is_valid_tuple(t):
        """ Custom validation function. We could also provide a callable """

        # (a) each item is a tuple of size 2
        # --you can reuse an entire method from the built-in lib when it supports direct calling mode
        instance_of(t, tuple)
        # --otherwise you can reuse a failure class, there are many
        if len(t) != 2: raise WrongLength(t, ref_length=2)

        # (b) the first element is a float between 0 and 1
        if not isinstance(t[0], Real): raise HasWrongType(t[0], Real)
        if not (0 <= t[0] <= 1):
            raise NotInRange(t[0], min_value=0, max_value=1)

        # (c) the second element is a lowercase string of size 3
        instance_of(t[1], str)
        if len(t[1]) != 3: raise WrongLength(t[1], ref_length=3)
        # -- finally you can write custom ValidationFailure types
        if not t[1].islower():
            raise NotLowerCase(t[1])
示例#21
0
def class_field_mini_lambda(value):
    from mini_lambda import s, Len
    from valid8 import validate_field
    from valid8.validation_lib import instance_of

    @validate_field('s', instance_of(str), Len(s) > 0, s.islower())
    class Foo:
        def __init__(self, s):
            self.s = s

    Foo(value)
def test_validate_field_custom_type():
    """"""
    from valid8 import validate_field, ClassFieldValidationError
    from valid8.validation_lib import instance_of, is_multiple_of
    from mini_lambda import x, s, Len

    class InvalidNameError(ClassFieldValidationError):
        help_msg = 'name should be a non-empty string'

    class InvalidSurfaceError(ClassFieldValidationError):
        help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'

    @validate_field('name', instance_of(str), Len(s) > 0,
                    error_type=InvalidNameError)
    @validate_field('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
                    error_type=InvalidSurfaceError)
    class House(object):
        """Properties work only with new-style classes"""
        def __init__(self, name, surface=None):
            self.name = name
            self.surface = surface

        @property
        def surface(self):
            return self.__surface

        @surface.setter
        def surface(self, surface=None):
            self.__surface = surface

    h = House('sweet home')
    h.name = ''  # DOES NOT RAISE InvalidNameError

    with pytest.raises(InvalidNameError):
        h = House('')

    h.surface = 100
    with pytest.raises(InvalidSurfaceError):
        h.surface = 10000
示例#23
0
def test_readme_index_usage_class_fields():
    """ Tests that the examples provided in the index page under Usage examples/class fields are correct"""

    from valid8 import validate_field, ClassFieldValidationError
    from valid8.validation_lib import instance_of, is_multiple_of
    from mini_lambda import x, s, Len

    @validate_field('name',
                    instance_of(str),
                    Len(s) > 0,
                    help_msg='name should be a non-empty string')
    @validate_field(
        'surface', (x >= 0) & (x < 10000),
        is_multiple_of(100),
        help_msg='Surface should be a multiple of 100 between 0 and 10000.')
    class House(object):
        def __init__(self, name, surface=None):
            self.name = name
            self.surface = surface

        @property
        def surface(self):
            return self.__surface

        @surface.setter
        def surface(self, surface=None):
            self.__surface = surface

    h = House('sweet home')
    h.name = ''  # DOES NOT RAISE InvalidNameError

    with pytest.raises(ClassFieldValidationError):
        h = House('')

    h.surface = 100
    with pytest.raises(ClassFieldValidationError):
        h.surface = 10000
示例#24
0
def function_input_mini_lambda(value):
    from mini_lambda import InputVar, Len
    from valid8 import validate_arg
    from valid8.validation_lib import instance_of
    from valid8.validation_lib.mini_lambda_ import Instance_of

    # just for fun: we create our custom mini_lambda variable named 't'
    t = InputVar('t', tuple)

    @validate_arg(
        't',
        instance_of(tuple),
        Len(t) == 2,
        # the first element is a float between 0 and 1
        Instance_of(t[0], Real),
        (0 <= t[0]) & (t[0] <= 1),
        # the 2d element is a lowercase string of len 3
        Instance_of(t[1], str),
        Len(t[1]) == 3,
        t[1].islower())
    def my_function(t):
        pass

    my_function(value)
def test_validate_tracebacks():
    """ Tests that the traceback is reduced for instance_of checks """

    from valid8 import validate_arg
    from valid8.validation_lib import instance_of
    from mini_lambda import x

    @validate_arg('foo', instance_of(int), x > 2)
    def dofoo(foo):
        pass

    # cause is none for HasWrongType
    with pytest.raises(ValidationError) as exc_info:
        dofoo("hello")

    e = exc_info.value
    assert e.__cause__.__cause__ is None

    # cause is not none otherwise
    with pytest.raises(ValidationError) as exc_info:
        dofoo(1)

    e = exc_info.value
    assert e.__cause__ is not None
示例#26
0
def test_readme_index_usage_basic():
    """ Tests that the examples provided in the index page under Usage examples/Basic are correct """

    from valid8 import assert_valid
    from valid8.validation_lib import instance_of, is_multiple_of

    surf = -1

    # (1) simplest: one named variable to validate, one validation function
    assert_valid('surface', surf, instance_of(int))
    with pytest.raises(ValidationError) as exc_info:
        assert_valid('surface', surf, is_multiple_of(100))
    e = exc_info.value
    assert str(e) == 'Error validating [surface=-1]. ' \
                     'IsNotMultipleOf: Value should be a multiple of 100. Wrong value: -1.'

    # (2) native mini_lambda support to define validation functions
    from mini_lambda import x
    with pytest.raises(ValidationError) as exc_info:
        assert_valid('surface', surf, x > 0)
    e = exc_info.value
    assert str(
        e
    ) == 'Error validating [surface=-1]. InvalidValue: Function [x > 0] returned [False] for value -1.'
def test_readme_index_combining_attrs():
    """ Tests that the examples provided in the index page under Combining/autoclass are correct """

    import attr
    from mini_lambda import s, x, Len
    from valid8 import validate_field, ClassFieldValidationError
    from valid8.validation_lib import instance_of, is_multiple_of

    class InvalidNameError(ClassFieldValidationError):
        help_msg = 'name should be a non-empty string'

    class InvalidSurfaceError(ClassFieldValidationError):
        help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'

    @validate_field('name',
                    instance_of(str),
                    Len(s) > 0,
                    error_type=InvalidNameError)
    @validate_field('surface', (x >= 0) & (x < 10000),
                    is_multiple_of(100),
                    error_type=InvalidSurfaceError)
    @attr.s
    class House(object):
        name = attr.ib()
        surface = attr.ib(default=None)

    h = House(
        'sweet home')  # Valid (surface is nonable by generated signature)

    h.name = ''  # DOES NOT RAISE InvalidNameError (no setter!)

    with pytest.raises(InvalidNameError):
        House('', 10000)  # InvalidNameError

    with pytest.raises(InvalidSurfaceError):
        House('sweet home', 10000)  # InvalidSurfaceError
def test_instance_of():
    """ tests that the instance_of() function works """

    # Basic function
    assert instance_of('r', str)
    assert instance_of(True, int)

    with pytest.raises(HasWrongType):
        instance_of(1, str)

    with pytest.raises(HasWrongType):
        instance_of('r', int)

    # Function generator
    assert instance_of(str)('r')
    assert instance_of(int)(True)

    with pytest.raises(HasWrongType):
        instance_of(str)(1)

    with pytest.raises(HasWrongType):
        instance_of(int)('r')

    assert_valid('instance', 'r', instance_of(str))
示例#29
0
class Round(GameNode):
    """
    A single round of Love Letter.

    Only the number of players and a deck is needed to initialise a Round.
    Instantiating a Round creates a list of :class:`RoundPlayer` s bound to this
    Round object.

    The main attributes/properties that make up the state of a Round are:
     - ``players``: A list of RoundPlayers (a physical player bound to this round).
                    The ID (:attr:`~loveletter.roundplayer.RoundPlayer.id`) of each
                    player corresponds to their index in this list.
     - ``living_players``: A subsequence of ``players`` containing only players that
                           are still alive in this round.
     - ``deck``: The deck cards are drawn from in this round.
     - ``discard_pile``: Central discard pile where discarded cards go.
     - ``state``: The current game state, an instance of :class:`RoundState`.
    """

    deck: Deck
    discard_pile: DiscardPile

    @valid8.validate_arg("num_players", instance_of(int))
    def __init__(self, num_players: int, deck: Deck = None):
        """
        Initialise a new round.

        :param num_players: Number of players in the round.
        :param deck: Initial deck to start with. None means use the standard deck.
        """
        players = [RoundPlayer(self, i) for i in range(num_players)]
        super().__init__(players)

        self.deck = deck if deck is not None else Deck.from_counts()
        self.discard_pile = DiscardPile([])

    @property
    def current_player(self) -> Optional[RoundPlayer]:
        """The player whose turn it currently is, or None if not started or ended."""
        return getattr(self.state, "current_player", None)

    @property
    def living_players(self) -> Sequence[RoundPlayer]:
        """The subsequence of living players."""
        return [p for p in self.players if p.alive]

    @property
    def targetable_players(self) -> Sequence[RoundPlayer]:
        """The subsequence of players that can be targeted (alive and not immune)."""
        return [p for p in self.players if p.alive and not p.immune]

    def get_player(self, player: RoundPlayer, offset: int):
        """
        Get the living player that is ``offset`` turns away from a given player.

        :param player: Reference point.
        :param offset: Offset from given player in number of turns. Can be negative,
                       in which case this searches for players in reverse turn order.
        :return: The requested player object.
        """
        players, living_players = self.players, self.living_players
        valid8.validate("player",
                        player,
                        is_in=players,
                        help_msg="Player is not in this round")
        valid8.validate(
            "living_players",
            living_players,
            min_len=1,
            help_msg="No living players remain",
        )
        if player.alive:
            idx = living_players.index(player)
            return living_players[(idx + offset) % len(living_players)]
        else:
            valid8.validate(
                "offset",
                offset,
                custom=lambda o: o != 0,
                help_msg=
                "Can't get player at offset 0; player itself is not alive",
            )
            idx = player.id
            nearest_living = next(p for p in players[idx:] + players[:idx]
                                  if p.alive)
            if offset > 0:
                offset -= 1
            living_idx = living_players.index(nearest_living)
            return living_players[(living_idx + offset) % len(living_players)]

    def next_player(self, player):
        """Get the next living player in turn order"""
        return self.get_player(player, 1)

    def previous_player(self, player):
        """Get the previous living player in turn order"""
        return self.get_player(player, -1)

    def play(self, **start_kwargs) -> GameEventGenerator:
        def iteration(self: Round) -> GameEventGenerator:
            player = self.current_player
            card = (yield from ChooseCardToPlay(player)).choice
            yield PlayingCard(player, card)
            results = yield from player.play_card(card)
            return results

        yield from super().play()
        return (yield from self._play_helper(iteration, **start_kwargs))

    def start(self, first_player: RoundPlayer = None) -> "Turn":
        """
        Start the round: hand out one card to each player and start the first turn.

        Can only be called once in the lifetime of a round object. If there aren't
        sufficient cards to start the round or the end condition is met immediately
        after starting, an error is raised.

        :param first_player: First player that will play in this round. None means
                             choose at random.
        """
        super().start()

        if first_player is None:
            first_player = random.choice(self.players)
        else:
            valid8.validate(
                "first_player",
                first_player,
                is_in=self.players,
                help_msg="Not a player of this round",
            )

        with valid8.validation(
                "round",
                self,
                help_msg="Invalid initial state for starting the round"):
            for player in itertools.islice(
                    cycle_from(self.players, first_player), None,
                    self.num_players + 1):
                self.deal_card(player)
            self._check_post_start()

        self.state = turn = Turn(first_player, turn_no=1)
        return turn

    def advance(self) -> GameNodeState:
        """Advance to the next turn (supposing it is possible to do so already)."""
        super().advance()
        if reason := self._reached_end():
            return self._finalize(reason=reason)

        # noinspection PyTypeChecker
        old_turn: Turn = self.state
        current = self.current_player
        assert current is not None
        next_player = self.next_player(current)
        assert next_player is not None
        # Reset immunity if needed:
        if next_player.immune:
            next_player.immune = False
        self.deal_card(next_player)
        self.state = new_turn = Turn(next_player, turn_no=old_turn.turn_no + 1)
        return new_turn
def make_converter(converter_def  # type: ConverterFuncDefinition
                   ):
    # type: (...) -> Converter
    """
    Makes a `Converter` from the provided converter object. Supported formats:

     - a `Converter`
     - a `<conversion_callable>` with possible signatures `(value)`, `(obj, value)`, `(obj, field, value)`.
     - a tuple `(<validation_callable>, <conversion_callable>)`
     - a tuple `(<valid_type>, <conversion_callable>)`

    If no name is provided and a `<conversion_callable>` is present, the callable name will be used as the converter
    name.

    The name of the conversion callable will be used in that case

    :param converter_def:
    :return:
    """
    try:
        nb_elts = len(converter_def)
    except (TypeError, FunctionDefinitionError):
        # -- single element
        # handle the special case of a LambdaExpression: automatically convert to a function
        if not is_mini_lambda(converter_def):
            if isinstance(converter_def, Converter):
                # already a converter
                return converter_def
            elif not callable(converter_def):
                raise ValueError('base converter function(s) not compliant with the allowed syntax. Base validation'
                                 ' function(s) can be %s Found %s.' % (supported_syntax, converter_def))
        # single element.
        return Converter.create_from_fun(converter_def)
    else:
        # -- a tuple
        if nb_elts == 1:
            converter_fun, validation_fun = converter_def[0], None
        elif nb_elts == 2:
            validation_fun, converter_fun = converter_def
            if validation_fun is not None:
                if isinstance(validation_fun, type):
                    # a type can be provided to denote accept "instances of <type>"
                    validation_fun = instance_of(validation_fun)
                elif validation_fun == JOKER_STR:
                    validation_fun = None
                else:
                    if not is_mini_lambda(validation_fun) and not callable(validation_fun):
                        raise ValueError('base converter function(s) not compliant with the allowed syntax. Validator '
                                         'is incorrect. Base converter function(s) can be %s Found %s.'
                                         % (supported_syntax, converter_def))
        else:
            raise ValueError(
                'tuple in converter_fun definition should have length 1, or 2. Found: %s' % (converter_def,))

        # check that the definition is valid
        if not is_mini_lambda(converter_fun) and not callable(converter_fun):
            raise ValueError('base converter function(s) not compliant with the allowed syntax. Base converter'
                             ' function(s) can be %s Found %s.' % (supported_syntax, converter_def))

        # finally create the failure raising callable
        return Converter.create_from_fun(converter_fun, validation_fun)