def test_dict_of_validator(value, key_type, value_type, should_pass): validate = dict_of(is_type(key_type), is_type(value_type)) if should_pass: assert validate(value), "dict_of validator failed for key type {}, item type {}, value {}".format(key_type, value_type, value) else: assert not validate(value, should_raise=False), "dict_of validator unexpectedly succeeded for key type {}, item type {}, value {}".format(key_type, value_type, value) with pytest.raises(TypeError): validate(value)
class IAMRole(Resource): resource_type = "AWS::IAM::Role" property_types = { "AssumeRolePolicyDocument": PropertyType(True, is_type(dict)), "ManagedPolicyArns": PropertyType(False, is_type(list)), "Path": PropertyType(False, is_str()), "Policies": PropertyType(False, is_type(list)), "PermissionsBoundary": PropertyType(False, is_str()), "Tags": PropertyType(False, list_of(is_type(dict))), } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
class ApiGatewayV2HttpApi(Resource): resource_type = "AWS::ApiGatewayV2::Api" property_types = { "Body": PropertyType(False, is_type(dict)), "BodyS3Location": PropertyType(False, is_type(dict)), "Description": PropertyType(False, is_str()), "FailOnWarnings": PropertyType(False, is_type(bool)), "BasePath": PropertyType(False, is_str()), "CorsConfiguration": PropertyType(False, is_type(dict)), } runtime_attrs = {"http_api_id": lambda self: ref(self.logical_id)}
class LambdaEventSourceMapping(Resource): resource_type = 'AWS::Lambda::EventSourceMapping' property_types = { 'BatchSize': PropertyType(False, is_type(int)), 'Enabled': PropertyType(False, is_type(bool)), 'EventSourceArn': PropertyType(True, is_str()), 'FunctionName': PropertyType(True, is_str()), 'StartingPosition': PropertyType(False, is_str()) } runtime_attrs = { "name": lambda self: ref(self.logical_id) }
class ApiGatewayApiKey(Resource): resource_type = "AWS::ApiGateway::ApiKey" property_types = { "CustomerId": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), "Enabled": PropertyType(False, is_type(bool)), "GenerateDistinctId": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "StageKeys": PropertyType(False, is_type(list)), "Value": PropertyType(False, is_str()), } runtime_attrs = {"api_key_id": lambda self: ref(self.logical_id)}
class IAMRole(Resource): resource_type = 'AWS::IAM::Role' property_types = { 'AssumeRolePolicyDocument': PropertyType(True, is_type(dict)), 'ManagedPolicyArns': PropertyType(False, is_type(list)), 'Path': PropertyType(False, is_str()), 'Policies': PropertyType(False, is_type(list)) } runtime_attrs = { "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") }
class LambdaLayerVersion(Resource): """Lambda layer version resource""" resource_type = "AWS::Lambda::LayerVersion" property_types = { "Content": PropertyType(True, is_type(dict)), "Description": PropertyType(False, is_str()), "LayerName": PropertyType(False, is_str()), "CompatibleRuntimes": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "LicenseInfo": PropertyType(False, is_str()), } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
class SamApplication(SamResourceMacro): """SAM application macro. """ APPLICATION_ID_KEY = 'ApplicationId' SEMANTIC_VERSION_KEY = 'SemanticVersion' resource_type = 'AWS::Serverless::Application' # The plugin will always insert the TemplateUrl parameter property_types = { 'Location': PropertyType(True, one_of(is_str(), is_type(dict))), 'TemplateUrl': PropertyType(False, is_str()), 'Parameters': PropertyType(False, is_type(dict)), 'NotificationARNs': PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), 'Tags': PropertyType(False, is_type(dict)), 'TimeoutInMinutes': PropertyType(False, is_type(int)) } def to_cloudformation(self, **kwargs): """Returns the stack with the proper parameters for this application """ nested_stack = self._construct_nested_stack() return [nested_stack] def _construct_nested_stack(self): """Constructs a AWS::CloudFormation::Stack resource """ nested_stack = NestedStack(self.logical_id, depends_on=self.depends_on, attributes=self.get_passthrough_resource_attributes()) nested_stack.Parameters = self.Parameters nested_stack.NotificationARNs = self.NotificationARNs application_tags = self._get_application_tags() nested_stack.Tags = self._construct_tag_list(self.Tags, application_tags) nested_stack.TimeoutInMinutes = self.TimeoutInMinutes nested_stack.TemplateURL = self.TemplateUrl if self.TemplateUrl else "" return nested_stack def _get_application_tags(self): """Adds tags to the stack if this resource is using the serverless app repo """ application_tags = {} if isinstance(self.Location, dict): if (self.APPLICATION_ID_KEY in self.Location.keys() and self.Location[self.APPLICATION_ID_KEY] is not None): application_tags[self._SAR_APP_KEY] = self.Location[self.APPLICATION_ID_KEY] if (self.SEMANTIC_VERSION_KEY in self.Location.keys() and self.Location[self.SEMANTIC_VERSION_KEY] is not None): application_tags[self._SAR_SEMVER_KEY] = self.Location[self.SEMANTIC_VERSION_KEY] return application_tags
def test_dict_of_validator(value, key_type, value_type, should_pass): validate = dict_of(is_type(key_type), is_type(value_type)) if should_pass: assert validate( value ), "dict_of validator failed for key type {}, item type {}, value {}".format( key_type, value_type, value) else: assert not validate( value, should_raise=False ), "dict_of validator unexpectedly succeeded for key type {}, item type {}, value {}".format( key_type, value_type, value) with pytest.raises(TypeError): validate(value)
class NestedStack(Resource): resource_type = 'AWS::CloudFormation::Stack' # TODO: support passthrough parameters for stacks (Conditions, etc) property_types = { 'TemplateURL': PropertyType(True, is_str()), 'Parameters': PropertyType(False, is_type(dict)), 'NotificationARNs': PropertyType(False, list_of(is_str())), 'Tags': PropertyType(False, list_of(is_type(dict))), 'TimeoutInMinutes': PropertyType(False, is_type(int)) } runtime_attrs = { "stack_id": lambda self: ref(self.logical_id) }
class ApiGatewayV2HttpApi(Resource): resource_type = 'AWS::ApiGatewayV2::Api' property_types = { 'Body': PropertyType(False, is_type(dict)), 'BodyS3Location': PropertyType(False, is_type(dict)), 'Description': PropertyType(False, is_str()), 'FailOnWarnings': PropertyType(False, is_type(bool)), 'BasePath': PropertyType(False, is_str()), 'Tags': PropertyType(False, list_of(is_type(dict))), 'CorsConfiguration': PropertyType(False, is_type(dict)) } runtime_attrs = { "http_api_id": lambda self: ref(self.logical_id), }
class EventsRule(Resource): resource_type = 'AWS::Events::Rule' property_types = { 'Description': PropertyType(False, is_str()), 'EventPattern': PropertyType(False, is_type(dict)), 'Name': PropertyType(False, is_str()), 'RoleArn': PropertyType(False, is_str()), 'ScheduleExpression': PropertyType(False, is_str()), 'State': PropertyType(False, is_str()), 'Targets': PropertyType(False, list_of(is_type(dict))) } runtime_attrs = { "rule_id": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") }
class CodeDeployApplication(Resource): resource_type = "AWS::CodeDeploy::Application" property_types = { "ComputePlatform": PropertyType(False, one_of(is_str(), is_type(dict))) } runtime_attrs = {"name": lambda self: ref(self.logical_id)}
class Route53RecordSetGroup(Resource): resource_type = "AWS::Route53::RecordSetGroup" property_types = { "HostedZoneId": PropertyType(False, is_str()), "HostedZoneName": PropertyType(False, is_str()), "RecordSets": PropertyType(False, is_type(list)), }
class SQSQueuePolicy(Resource): resource_type = 'AWS::SQS::QueuePolicy' property_types = { 'PolicyDocument': PropertyType(True, is_type(dict)), 'Queues': PropertyType(True, list_of(str)), } runtime_attrs = {"arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
class ApiGatewayDeployment(Resource): resource_type = 'AWS::ApiGateway::Deployment' property_types = { 'Description': PropertyType(False, is_str()), 'RestApiId': PropertyType(True, is_str()), 'StageDescription': PropertyType(False, is_type(dict)), 'StageName': PropertyType(False, is_str()) } runtime_attrs = { "deployment_id": lambda self: ref(self.logical_id), } def make_auto_deployable(self, stage, swagger=None): """ Sets up the resource such that it will triggers a re-deployment when Swagger changes :param swagger: Dictionary containing the Swagger definition of the API """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat generator = logical_id_generator.LogicalIdGenerator( self.logical_id, str(swagger)) self.logical_id = generator.gen() hash = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(hash) stage.update_deployment_ref(self.logical_id)
class Schedule(PushEventSource): """Scheduled executions for SAM Functions.""" resource_type = 'Schedule' principal = 'events.amazonaws.com' property_types = { 'Schedule': PropertyType(True, is_str()), 'Input': PropertyType(False, is_str()), 'Enabled': PropertyType(False, is_type(bool)), 'Name': PropertyType(False, is_str()), 'Description': PropertyType(False, is_str()) } def to_cloudformation(self, **kwargs): """Returns the CloudWatch Events Rule and Lambda Permission to which this Schedule event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands :rtype: list """ function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] events_rule = EventsRule(self.logical_id) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule if self.Enabled is not None: events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description events_rule.Targets = [self._construct_target(function)] source_arn = events_rule.get_runtime_attr("arn") if CONDITION in function.resource_attributes: events_rule.set_resource_attribute( CONDITION, function.resource_attributes[CONDITION]) resources.append( self._construct_permission(function, source_arn=source_arn)) return resources def _construct_target(self, function): """Constructs the Target property for the CloudWatch Events Rule. :returns: the Target property :rtype: dict """ target = { 'Arn': function.get_runtime_attr("arn"), 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input return target
def test_list_of_validator(value, item_type, should_pass): validate = list_of(is_type(item_type)) if should_pass: assert validate(value), "list_of validator failed for item type {}, value {}".format(item_type, value) else: assert not validate(value, should_raise=False), "list_of validator unexpectedly succeeded for item type {}, value {}".format(item_type, value) with pytest.raises(TypeError): validate(value)
class SNSSubscription(Resource): resource_type = 'AWS::SNS::Subscription' property_types = { 'Endpoint': PropertyType(True, is_str()), 'Protocol': PropertyType(True, is_str()), 'TopicArn': PropertyType(True, is_str()), 'FilterPolicy': PropertyType(False, is_type(dict)) }
class ApiGatewayDomainName(Resource): resource_type = "AWS::ApiGateway::DomainName" property_types = { "RegionalCertificateArn": PropertyType(False, is_str()), "DomainName": PropertyType(True, is_str()), "EndpointConfiguration": PropertyType(False, is_type(dict)), "CertificateArn": PropertyType(False, is_str()), }
class IotTopicRule(Resource): resource_type = 'AWS::IoT::TopicRule' property_types = {'TopicRulePayload': PropertyType(False, is_type(dict))} runtime_attrs = { "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") }
class EventsRule(Resource): resource_type = "AWS::Events::Rule" property_types = { "Description": PropertyType(False, is_str()), "EventBusName": PropertyType(False, is_str()), "EventPattern": PropertyType(False, is_type(dict)), "Name": PropertyType(False, is_str()), "RoleArn": PropertyType(False, is_str()), "ScheduleExpression": PropertyType(False, is_str()), "State": PropertyType(False, is_str()), "Targets": PropertyType(False, list_of(is_type(dict))), } runtime_attrs = { "rule_id": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") }
class ApiGatewayDomainName(Resource): resource_type = 'AWS::ApiGateway::DomainName' property_types = { 'RegionalCertificateArn': PropertyType(False, is_str()), 'DomainName': PropertyType(True, is_str()), 'EndpointConfiguration': PropertyType(False, is_type(dict)), 'CertificateArn': PropertyType(False, is_str()) }
class CloudWatchEvent(PushEventSource): """CloudWatch Events event source for SAM Functions.""" resource_type = 'CloudWatchEvent' principal = 'events.amazonaws.com' property_types = { 'EventBusName': PropertyType(False, is_str()), 'Pattern': PropertyType(False, is_type(dict)), 'Input': PropertyType(False, is_str()), 'InputPath': PropertyType(False, is_str()) } def to_cloudformation(self, **kwargs): """Returns the CloudWatch Events Rule and Lambda Permission to which this CloudWatch Events event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events event expands :rtype: list """ function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern events_rule.Targets = [self._construct_target(function)] if CONDITION in function.resource_attributes: events_rule.set_resource_attribute( CONDITION, function.resource_attributes[CONDITION]) resources.append(events_rule) source_arn = events_rule.get_runtime_attr("arn") resources.append( self._construct_permission(function, source_arn=source_arn)) return resources def _construct_target(self, function): """Constructs the Target property for the CloudWatch Events Rule. :returns: the Target property :rtype: dict """ target = { 'Arn': function.get_runtime_attr("arn"), 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input if self.InputPath is not None: target['InputPath'] = self.InputPath return target
class CognitoUserPool(Resource): resource_type = "AWS::Cognito::UserPool" property_types = { "AdminCreateUserConfig": PropertyType(False, is_type(dict)), "AliasAttributes": PropertyType(False, list_of(is_str())), "AutoVerifiedAttributes": PropertyType(False, list_of(is_str())), "DeviceConfiguration": PropertyType(False, is_type(dict)), "EmailConfiguration": PropertyType(False, is_type(dict)), "EmailVerificationMessage": PropertyType(False, is_str()), "EmailVerificationSubject": PropertyType(False, is_str()), "LambdaConfig": PropertyType(False, is_type(dict)), "MfaConfiguration": PropertyType(False, is_str()), "Policies": PropertyType(False, is_type(dict)), "Schema": PropertyType(False, list_of(dict)), "SmsAuthenticationMessage": PropertyType(False, is_str()), "SmsConfiguration": PropertyType(False, list_of(dict)), "SmsVerificationMessage": PropertyType(False, is_str()), "UsernameAttributes": PropertyType(False, list_of(is_str())), "UserPoolAddOns": PropertyType(False, list_of(dict)), "UserPoolName": PropertyType(False, is_str()), "UserPoolTags": PropertyType(False, is_type(dict)), "VerificationMessageTemplate": PropertyType(False, is_type(dict)), } runtime_attrs = { "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn"), "provider_name": lambda self: fnGetAtt(self.logical_id, "ProviderName"), "provider_url": lambda self: fnGetAtt(self.logical_id, "ProviderURL"), }
class SNSSubscription(Resource): resource_type = "AWS::SNS::Subscription" property_types = { "Endpoint": PropertyType(True, is_str()), "Protocol": PropertyType(True, is_str()), "TopicArn": PropertyType(True, is_str()), "Region": PropertyType(False, is_str()), "FilterPolicy": PropertyType(False, is_type(dict)), }
class Schedule(EventSource): """Scheduled executions for SAM State Machine.""" resource_type = "Schedule" principal = "events.amazonaws.com" property_types = { "Schedule": PropertyType(True, is_str()), "Input": PropertyType(False, is_str()), "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), } def to_cloudformation(self, resource, **kwargs): """Returns the EventBridge Rule and IAM Role to which this Schedule event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands :rtype: list """ resources = [] permissions_boundary = kwargs.get("permissions_boundary") events_rule = EventsRule(self.logical_id) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule if self.Enabled is not None: events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description if CONDITION in resource.resource_attributes: events_rule.set_resource_attribute( CONDITION, resource.resource_attributes[CONDITION]) role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] return resources def _construct_target(self, resource, role): """Constructs the Target property for the EventBridge Rule. :returns: the Target property :rtype: dict """ target = { "Arn": resource.get_runtime_attr("arn"), "Id": self.logical_id + "StepFunctionsTarget", "RoleArn": role.get_runtime_attr("arn"), } if self.Input is not None: target["Input"] = self.Input return target
class StepFunctionsStateMachine(Resource): resource_type = "AWS::StepFunctions::StateMachine" property_types = { "Definition": PropertyType(False, is_type(dict)), "DefinitionString": PropertyType(False, is_str()), "DefinitionS3Location": PropertyType(False, is_type(dict)), "LoggingConfiguration": PropertyType(False, is_type(dict)), "RoleArn": PropertyType(True, is_str()), "StateMachineName": PropertyType(False, is_str()), "StateMachineType": PropertyType(False, is_str()), "Tags": PropertyType(False, list_of(is_type(dict))), "DefinitionSubstitutions": PropertyType(False, is_type(dict)), } runtime_attrs = { "arn": lambda self: ref(self.logical_id), "name": lambda self: fnGetAtt(self.logical_id, "Name"), }
class ApiGatewayDeployment(Resource): _X_HASH_DELIMITER = "||" resource_type = "AWS::ApiGateway::Deployment" property_types = { "Description": PropertyType(False, is_str()), "RestApiId": PropertyType(True, is_str()), "StageDescription": PropertyType(False, is_type(dict)), "StageName": PropertyType(False, is_str()), } runtime_attrs = {"deployment_id": lambda self: ref(self.logical_id)} def make_auto_deployable(self, stage, openapi_version=None, swagger=None, domain=None, redeploy_restapi_parameters=None): """ Sets up the resource such that it will trigger a re-deployment when Swagger changes or the openapi version changes or a domain resource changes. :param swagger: Dictionary containing the Swagger definition of the API :param openapi_version: string containing value of OpenApiVersion flag in the template :param domain: Dictionary containing the custom domain configuration for the API :param redeploy_restapi_parameters: Dictionary containing the properties for which rest api will be redeployed """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat hash_input = [str(swagger)] if openapi_version: hash_input.append(str(openapi_version)) if domain: hash_input.append(json.dumps(domain)) if redeploy_restapi_parameters: function_names = redeploy_restapi_parameters.get("function_names") else: function_names = None # The deployment logical id is <api logicalId> + "Deployment" # The keyword "Deployment" is removed and all the function names associated with api is obtained if function_names and function_names.get(self.logical_id[:-10], None): hash_input.append(function_names.get(self.logical_id[:-10], "")) data = self._X_HASH_DELIMITER.join(hash_input) generator = logical_id_generator.LogicalIdGenerator( self.logical_id, data) self.logical_id = generator.gen() digest = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(digest) stage.update_deployment_ref(self.logical_id)
class CloudWatchEvent(EventSource): """CloudWatch Events/EventBridge event source for SAM State Machine.""" resource_type = "CloudWatchEvent" principal = "events.amazonaws.com" property_types = { "EventBusName": PropertyType(False, is_str()), "Pattern": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), } def to_cloudformation(self, resource, **kwargs): """Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this CloudWatch Events/EventBridge event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events/EventBridge event expands :rtype: list """ resources = [] permissions_boundary = kwargs.get("permissions_boundary") events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern if CONDITION in resource.resource_attributes: events_rule.set_resource_attribute( CONDITION, resource.resource_attributes[CONDITION]) resources.append(events_rule) role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] return resources def _construct_target(self, resource, role): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property :rtype: dict """ target = { "Arn": resource.get_runtime_attr("arn"), "Id": self.logical_id + "StepFunctionsTarget", "RoleArn": role.get_runtime_attr("arn"), } if self.Input is not None: target["Input"] = self.Input if self.InputPath is not None: target["InputPath"] = self.InputPath return target
def test_is_type_validator(): example_properties = [ (1, int), ("Hello, World!", str), ({'1': 1}, dict), (DummyType(), DummyType) ] for value, value_type in example_properties: # Check that is_type(value_type) passes for value validate = is_type(value_type) assert validate(value), "is_type validator failed for type {}, value {}".format(value_type, value) # For every non-matching type, check that is_type(other_type) raises TypeError for _, other_type in example_properties: if value_type != other_type: validate = is_type(other_type) assert not validate(value, should_raise=False), "is_type validator unexpectedly succeeded for type {}, value {}".format(value_type, value) with pytest.raises(TypeError): validate(value)
class ApiGatewayStage(Resource): resource_type = 'AWS::ApiGateway::Stage' property_types = { 'CacheClusterEnabled': PropertyType(False, is_type(bool)), 'CacheClusterSize': PropertyType(False, is_str()), 'ClientCertificateId': PropertyType(False, is_str()), 'DeploymentId': PropertyType(True, is_str()), 'Description': PropertyType(False, is_str()), 'RestApiId': PropertyType(True, is_str()), 'StageName': PropertyType(True, one_of(is_str(), is_type(dict))), 'Variables': PropertyType(False, is_type(dict)), "MethodSettings": PropertyType(False, is_type(list)) } runtime_attrs = { "stage_name": lambda self: ref(self.logical_id), } def update_deployment_ref(self, deployment_logical_id): self.DeploymentId = ref(deployment_logical_id)
# Dict of mixed keys and values ({ '1': '1', 2: 2 }, str, int, False), # Not a dict (('1', 2), str, int, False) ]) def test_dict_of_validator(value, key_type, value_type, should_pass): validate = dict_of(is_type(key_type), is_type(value_type)) if should_pass: assert validate(value), "dict_of validator failed for key type {}, item type {}, value {}".format(key_type, value_type, value) else: assert not validate(value, should_raise=False), "dict_of validator unexpectedly succeeded for key type {}, item type {}, value {}".format(key_type, value_type, value) with pytest.raises(TypeError): validate(value) @pytest.mark.parametrize('value,validators,should_pass', [ # Value of first expected type (1, [ is_type(int), list_of(is_type(int)) ], True), # Value of second expected type ([ 1, 2, 3 ], [ is_type(int), list_of(is_type(int)) ], True), # Value of neither expected type ("Hello, World!", [ is_type(int), list_of(is_type(int)) ], False) ]) def test_one_of_validator(value, validators, should_pass): validate = one_of(*validators) if should_pass: assert validate(value), "one_of validator failed for validators {}, value {}".format(validators, value) else: assert not validate(value, should_raise=False), "one_of validator unexpectedly succeeded for validators {}, value {}".format(validators, value) with pytest.raises(TypeError): validate(value)