def test_numpy_nan_like_lengths(): """ Test that a strange int length with bad behaviour is correctly handled """ # Actually the test below shows that in current versions of python it is not possible to create a len class NanInt(int): """ An int that behaves like numpy NaN (comparison always returns false) """ def __le__(self, other): return False def __lt__(self, other): return False def __gt__(self, other): return False def __ge__(self, other): return False nanlength = NanInt() class Foo: def __len__(self): return nanlength if isinstance(len(Foo()), NanInt): # in current version of python this does not happen, but the test is ready for future evolutions with pytest.raises(ValidationError) as exc_info: validate('Foo()', Foo(), min_len=0, max_len=10)
def test_numpy_nan(): """ Test that a numpy nan is correctly handled """ import numpy as np with pytest.raises(ValidationError) as exc_info: validate('np.nan', np.nan, min_value=5.1, max_value=5.2)
def yield_helper( self, ) -> Generator["GameInputRequest", "GameInputRequest", "GameInputRequest"]: """ A helper generator that yields this input request with validation. It yields a single object ``self``. When execution resumes, it checks that ``self`` was sent back and was fulfilled. Finally, if validation completes successfully, the return value of the generator is, again, ``self``. """ completed = yield self valid8.validate( "completed_step", completed, custom=lambda o: o is self, help_msg=( f"Did not receive the same GameInputRequest that was yielded: " f"expected {self}, got {completed}"), ) valid8.validate( "completed_step", completed, custom=lambda r: r.fulfilled, help_msg="Received an unfulfilled GameInputRequest step", ) return completed
def validate( cls, name, # type: str val): """ Class method that can be used to check if some value is valid. A name should be provided so that the error messages are human-friendly. :param name: :param val: :return: """ # validate type for typ in cls.__type__: validate(name, val, instance_of=typ, help_msg=cls.__help_msg__, error_type=cls.__error_type__) # apply validators if cls._validator is not None: cls._validator.assert_valid(name, val, help_msg=cls.__help_msg__, error_type=cls.__error_type__)
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 test_validate_(): """ Tests the validate function """ # nominal surf = 2 validate('surface', surf, instance_of=int, min_value=0) validate('surface', surf, instance_of=(int, ), min_value=0) validate('surface', surf, instance_of=(int, str), min_value=0) # error wrong value surf = -1 with pytest.raises(ValidationError) as exc_info: validate('surface', surf, instance_of=int, min_value=0) e = exc_info.value assert str(e) == "Error validating [surface=-1]. " \ "TooSmall: x >= 0 does not hold for x=-1. Wrong value: -1." # error wrong type surf = 1j with pytest.raises(ValidationError) as exc_info: # note: we use a length-1 tuple on purpose to see if the error msg takes length into account validate('surface', surf, instance_of=(int, ), min_value=0) e = exc_info.value assert str(e) == "Error validating [surface=1j]. " \ "HasWrongType: Value should be an instance of %r. Wrong value: 1j." % int
def _get_valid_blob_path_prefix(blob_path_prefix # type: str ): # type: (...) -> str """ Utility method to get a valid blob path prefix from a provided one. A trailing slash is added if non-empty :param blob_path_prefix: :return: """ validate('blob_path_prefix', blob_path_prefix, instance_of=str, enforce_not_none=False) if blob_path_prefix is None: blob_path_prefix = '' elif isinstance(blob_path_prefix, str): if len(blob_path_prefix) > 0 and not blob_path_prefix.endswith('/'): blob_path_prefix = blob_path_prefix + '/' else: raise TypeError( "Blob path prefix should be a valid string or not be provided (default is empty string)" ) return blob_path_prefix
def csv_to_df( csv_buffer_or_str_or_filepath, # type: Union[str, StringIO, BytesIO] csv_name=None # type: str ): # type: (...) -> pandas.DataFrame """ Converts the provided csv to a DatFrame, typically to read it from blob storage for Batch AzureML calls. Helper method to ensure consistent reading in particular for timezones and datetime parsing :param csv_buffer_or_str_or_filepath: :param csv_name: the name of the DataFrame, for error messages :return: """ validate(csv_name, csv_buffer_or_str_or_filepath) # pandas does not accept string. create a buffer if isinstance(csv_buffer_or_str_or_filepath, str): csv_buffer_or_str_or_filepath = create_reading_buffer( csv_buffer_or_str_or_filepath) # read without parsing dates res = pandas.read_csv(csv_buffer_or_str_or_filepath, sep=',', decimal='.') # infer_dt_format=True, parse_dates=[0] # -- try to infer datetime columns convert_all_datetime_columns(res) # -- additionally we automatically configure the timezone as UTC localize_all_datetime_columns(res) return res
def dfs_to_azmltables( dfs, # type: Dict[str, pandas.DataFrame] swagger_format=False, # type: bool mimic_azml_output=False, # type: bool replace_NaN_with=None, # type: Any replace_NaT_with=None, # type: Any ): # type: (...) -> Dict[str, Dict[str, Union[str, Dict[str, List]]]] """ Converts a dictionary of DataFrames into a dictionary of dictionaries following the structure required for AzureML JSON conversion :param dfs: a dictionary containing input names and input content (each input content is a DataFrame) :param swagger_format: a boolean (default: False) indicating if the 'swagger' azureml format should be used :return: a dictionary of tables represented as dictionaries """ validate('dfs', dfs, instance_of=dict) # resultsDict = {} # for dfName, df in DataFramesDict.items(): # resultsDict[dfName] = Df_to_AzmlTable(df, dfName) # return resultsDict return { df_name: df_to_azmltable(df, table_name=df_name, swagger_format=swagger_format, mimic_azml_output=mimic_azml_output, replace_NaN_with=replace_NaN_with, replace_NaT_with=replace_NaT_with) for df_name, df in dfs.items() }
def df_to_csv( df, # type: pandas.DataFrame df_name=None, # type: str charset=None # type: str ): # type: (...) -> str """ Converts the provided DataFrame to a csv, typically to store it on blob storage for Batch AzureML calls. WARNING: datetime columns are converted in ISO format but the milliseconds are ignored and set to zero. :param df: :param df_name: the name of the DataFrame, for error messages :param charset: the charset to use for encoding :return: """ validate(df_name, df, instance_of=pandas.DataFrame) # TODO what about timezone detail if not present, will the %z be ok ? return df.to_csv(path_or_buf=None, sep=',', decimal='.', na_rep='', encoding=charset, index=False, date_format='%Y-%m-%dT%H:%M:%S.000%z')
def call_azureml( self, service_id, # type: str service_config, # type: ServiceConfig ws_inputs, # type: Dict[str, pd.DataFrame] ws_params=None, # type: Dict[str, str] ws_output_names=None, # type: List[str] session=None, # type: Session ): """ (See super for base description) :return: """ validate("%s:base_url" % service_id, service_config.base_url) validate("%s:api_key" % service_id, service_config.api_key) validate("%s:blob_account" % service_id, service_config.blob_account) validate("%s:blob_api_key" % service_id, service_config.blob_api_key) validate("%s:blob_container" % service_id, service_config.blob_container) return execute_bes( # all of this is filled using the `service_config` api_key=service_config.api_key, base_url=service_config.base_url, blob_storage_account=service_config.blob_account, blob_storage_apikey=service_config.blob_api_key, blob_container=service_config.blob_container, blob_path_prefix=service_config.blob_path_prefix, # blob_charset=None, # ------- inputs=ws_inputs, params=ws_params, output_names=ws_output_names, nb_seconds_between_status_queries=self.polling_period_seconds, requests_session=session)
def _add_entry(self, value: Entry, create_key: Any) -> None: validate('create_key', create_key, custom=Menu.Builder.is_valid_key) validate('value.key', value.key, custom=lambda v: v not in self.__key2entry) self.__entries.append(value) self.__key2entry[value.key] = value
def _validate_choice(self, value): valid8.validate( "choice", value, is_in=self.options, help_msg="This card is not an option", )
def __post_init__(self): validate_dataclass(self) validate('MenuDescription.value', self.value, min_len=1, max_len=1000, custom=pattern(r'[0-9A-Za-z ;.,_-]*'))
def __post_init__(self): validate_dataclass(self) validate('Key.value', self.value, min_len=1, max_len=10, custom=pattern(r'[0-9A-Za-z_-]*'))
def _validate_choice(self, value): valid8.validate( "target", value, is_in=self._valid_choices, help_msg="Must target a living, non-immune player from the round", )
def __fetch(self) -> None: res = requests.get(url=f'{api_server}shopping-list/', headers={'Authorization': f'Token {self.__key}'}) if res.status_code != 200: raise RuntimeError() json = res.json() for item in json: validate('row length', item, length=7) item_id = int(item['id']) name = Name(str(item['name'])) category = str(item['category']) manufacturer = Manufacturer(str(item['manufacturer'])) price = Price.create(int(int(item['price']) / 100), int(item['price']) % 100) quantity = Quantity(int(item['quantity'])) self.__id_dictionary.append([item_id, name.value, manufacturer.value]) description = Description(str(item['description'])) if category == 'Smartphone': self.__shoppinglist.add_smartphone(Smartphone(name, manufacturer, price, quantity, description)) elif category == 'Computer': self.__shoppinglist.add_computer(Computer(name, manufacturer, price, quantity, description)) else: raise ValueError('Unknown item category in your shopping list')
def _validate_choice(self, value): valid8.validate( "choice", value, is_in=self.round.players, help_msg="Not a player of the round", )
def inline_validate_3(s): from valid8 import validate # we create a custom mini_lambda variable, since the name 's' is already used from mini_lambda import InputVar txt = InputVar('txt', str) validate('s', s, instance_of=str, min_len=1, custom=txt.islower())
def test_numpy_bool_is_ok(): """ Tests that numpy booleans can be used as success result by validation functions""" # a numpy float is not a python float np_float = np.float_(1.0) # numpy bools are not python bools assert np_float > 0 is not True assert 'numpy' in "%s" % (np_float > 0).__class__ # simple checker validate('np_float', np_float, min_value=1) # more complex checker @validate_arg('a', lambda x: x >= 1) def foo(a): return foo(np_float) # even more complex checker @validate_arg('a', and_(x > 0, lambda x: x >= 1)) def foo(a): return foo(np_float)
def create_blob_refs( blob_service, # type: BlockBlobService blob_container, # type: str blob_names, # type: List[str] blob_path_prefix=None, # type: str blob_name_prefix=None # type: str ): # type: (...) -> Dict[str, AzmlBlobTable] """ Utility method to create one or several blob references on the same container on the same blob storage service. :param blob_service: :param blob_container: :param blob_names: :param blob_path_prefix: optional prefix to the blob names :param blob_name_prefix: :return: """ validate('blob_names', blob_names, instance_of=list) if blob_name_prefix is None: blob_name_prefix = "" else: validate('blob_name_prefix', blob_name_prefix, instance_of=str) # convert all and return in a dict return { blob_name: create_blob_ref(blob_service, blob_container, blob_name_prefix + blob_name, blob_path_prefix=blob_path_prefix)[0] for blob_name in blob_names }
def _validate_choice(self, value): valid8.validate("choice", value, instance_of=tuple) valid8.validate( "choice", Counter(value), equals=Counter(self.cards), help_msg="Chosen cards don't match cards to be ordered", )
def __post_init__(self): validate_dataclass(self) validate('neighbourhood', self.neighbourhood, is_in={ 'NRV', 'MTA', 'MTB', 'MLA', 'MLB', 'MSN', 'CH1', 'CH2', 'MON', 'SNG' }) validate('room_type', self.room, is_in={'SIN', 'DBL'})
def to_serializable(self) -> Serializable: """Return a serializable value representing this choice.""" valid8.validate( "fulfilled", self.fulfilled, equals=True, help_msg="Choice hasn't been set yet", ) return self.choice
def test_enum_isinstance(): """Tests that enum can be used in validate/instance_of""" from enum import Enum class MyEnum(Enum): a = 1 b = 2 validate('a', MyEnum.a, instance_of=MyEnum)
def __enter__(self): valid8.validate( "turn.stage", self.stage, equals=Turn.Stage.START, help_msg= f"Can't start another move; turn is already {self.stage.name}", ) self._set_stage(Turn.Stage.IN_PROGRESS)
def check_move(self, owner, card): disallowed = {CardType.PRINCE, CardType.KING} card_type = CardType(card) valid8.validate( "card", card_type, custom=lambda t: t not in disallowed, help_msg=f"Can't play a {card_type.name.title()} with a Countess in hand", )
def nb_floors( self, nb_floors # type: Optional[Integral] ): validate('nb_floors', nb_floors, instance_of=Integral, enforce_not_none=False) self._surface = nb_floors # !**
def _validate_choice(self, value): from loveletter.cards import CardType card_type = CardType(value) valid8.validate( "card_type", card_type, custom=lambda t: t != CardType.GUARD, # TODO: use minilambda help_msg="You can't guess a Guard", )
def replace(self, card: Card): """Replace the current (only) card in the hand; return the old card""" valid8.validate( "hand", self._cards, length=1, help_msg="Can't replace hand with more than one card", ) old, self._cards[0] = self._cards[0], card return old