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()
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)
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
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)
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)
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)
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()
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)
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)
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)
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)
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)
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()
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)
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)
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])
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
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
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
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))
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)