Package: yamale
Yamale (ya·ma·lē):warning: Ensure that your schema definitions come from internal or trusted sources. Yamale does not protect against intentionally malicious schemas.A schema and validator for YAML.What's YAML? See the current spechereand an introduction
to the syntaxhere.RequirementsPython 3.6+PyYAMLruamel.yaml (optional)Installpip$pipinstallyamaleNOTE: Some platforms, e.g., Mac OS, may ship with only Python 2 and may not have pip installed.
Installation of Python 3 should also install pip. To preserve any system dependencies on default
software, consider installing Python 3 as a local package. Please note replacing system-provided
Python may disrupt other software. Mac OS users may wish to investigate MacPorts, homebrew, or
building Python 3 from source; in all three cases, Apple's Command Line Tools (CLT) for Xcode
may be required. See alsodevelopers, below.ManualDownload Yamale from:https://github.com/23andMe/Yamale/archive/master.zipUnzip somewhere temporaryRunpython setup.py install(may have to prependsudo)UsageCommand lineYamale can be run from the command line to validate one or many YAML files. Yamale will search the
directory you supply (current directory is default) for YAML files. Each YAML file it finds it will
look in the same directory as that file for its schema, if there is no schema Yamale will keep
looking up the directory tree until it finds one. If Yamale can not find a schema it will tell you.Usage:usage:yamale[-h][-sSCHEMA][-nCPU_NUM][-pPARSER][--no-strict][PATH]Validateyamlfiles.

positionalarguments:PATHfoldertovalidate.Defaultiscurrentdirectory.

optionalarguments:-h,--helpshowthishelpmessageandexit-sSCHEMA,--schemaSCHEMAfilenameofschema.Defaultisschema.yaml.-nCPU_NUM,--cpu-numCPU_NUMnumberofCPUstouse.Defaultis4.-pPARSER,--parserPARSERYAMLlibrarytoloadfiles.Choicesare"ruamel"or"pyyaml"(default).--no-strictDisablestrictmode,unexpectedelementsinthedatawillbeaccepted.APIThere are several ways to feed Yamale schema and data files. The simplest way is to let Yamale take
care of reading and parsing your YAML files.All you need to do is supply the files' path:# Import Yamale and make a schema object:importyamaleschema=yamale.make_schema('./schema.yaml')# Create a Data objectdata=yamale.make_data('./data.yaml')# Validate data against the schema. Throws a ValueError if data is invalid.yamale.validate(schema,data)You can pass a string of YAML tomake_schema()andmake_data()instead of passing a file path
by using thecontent=parameter:data=yamale.make_data(content="""name: Billage: 26height: 6.2awesome: True""")Ifdatais valid, nothing will happen. However, ifdatais invalid Yamale will throw aYamaleErrorwith a message containing all the invalid nodes:try:yamale.validate(schema,data)print('Validation success! 👍')exceptValueErrorase:print('Validation failed!\n%s'%str(e))exit(1)and an array ofValidationResult.try:yamale.validate(schema,data)print('Validation success! 👍')exceptYamaleErrorase:print('Validation failed!\n')forresultine.results:print("Error validating data '%s' with '%s'\n\t"%(result.data,result.schema))forerrorinresult.errors:print('\t%s'%error)exit(1)You can also specify an optionalparserif you'd like to use theruamel.yaml(YAML 1.2 support) instead:# Import Yamale and make a schema object, make sure ruamel.yaml is installed already.importyamaleschema=yamale.make_schema('./schema.yaml',parser='ruamel')# Create a Data objectdata=yamale.make_data('./data.yaml',parser='ruamel')# Validate data against the schema same as before.yamale.validate(schema,data)Schema:warning: Ensure that your schema definitions come from internal or trusted sources. Yamale does not protect against intentionally malicious schemas.To use Yamale you must make a schema. A schema is a valid YAML file with one or more documents
inside. Each node terminates in a string which contains valid Yamale syntax. For example,str()represents aString validator.A basic schema:name:str()age:int(max=200)height:num()awesome:bool()And some YAML that validates:name:Billage:26height:6.2awesome:TrueTake a look at theExamplessection for more complex schema ideas.IncludesSchema files may contain more than one YAML document (nodes separated by---). The first document
found will be the base schema. Any additional documents will be treated as Includes. Includes allow
you to define a valid structure once and use it several times. They also allow you to do recursion.A schema with an Include validator:person1:include('person')person2:include('person')---person:name:str()age:int()Some valid YAML:person1:name:Billage:70person2:name:Jillage:20Every root node not in the first YAML document will be treated like an include:person:include('friend')group:include('family')---friend:name:str()family:name:str()Is equivalent to:person:include('friend')group:include('family')---friend:name:str()---family:name:str()RecursionYou can get recursion using the Include validator.This schema:person:include('human')---human:name:str()age:int()friend:include('human', required=False)Will validate this data:person:name:Billage:50friend:name:Jillage:20friend:name:Willage:10Adding external includesAfter you construct a schema you can add extra, external include definitions by callingschema.add_include(dict). This method takes a dictionary and adds each key as another include.Strict modeBy default Yamale will provide errors for extra elements present in lists and maps that are not
covered by the schema. With strict mode disabled (using the--no-strictcommand line option),
additional elements will not cause any errors. In the API, strict mode can be toggled by passing
the strict=True/False flag to the validate function.It is possible to mix strict and non-strict mode by setting the strict=True/False flag in the
include validator, setting the option only for the included validators.ValidatorsHere are all the validators Yamale knows about. Every validator takes arequiredkeyword telling
Yamale whether or not that node must exist. By default every node is required. Example:str(required=False)You can also require that an optional value is notNoneby using thenonekeyword. By default
Yamale will acceptNoneas a valid value for a key that's not required. RejectNonevalues
withnone=Falsein any validator. Example:str(required=False, none=False).Some validators take keywords and some take arguments, some take both. For instance theenum()validator takes one or more constants as arguments and therequiredkeyword:enum('a string', 1, False, required=False)String -str(min=int, max=int, equals=string, starts_with=string, ends_with=string, matches=regex, exclude=string, ignore_case=False, multiline=False, dotall=False)Validates strings.keywordsmin: len(string) >= minmax: len(string) <= maxequals: string == value (addignore_case=Truefor case-insensitive checking)starts_with: Accepts only strings starting with given value (addignore_case=Truefor
case-insensitive checking)matches: Validates the string against a given regex. Similar to theregex()validator,
you can useignore_case,multilineanddotall)ends_with: Accepts only strings ending with given value (addignore_case=Truefor case-insensitive checking)exclude: Rejects strings that contains any character in the excluded valueignore_case: Validates strings in a case-insensitive manner.multiline:^and$in a pattern match at the beginning and end of each line in a string
in addition to matching at the beginning and end of the entire string. (A pattern matches
atthe beginning of a stringeven in
multiline mode; see below for a workaround.); only allowed in conjunction with amatcheskeyword.dotall:.in a pattern matches newline characters in a validated string in addition to
matching every character thatisn'ta newline.; only allowed in conjunction with amatcheskeyword.Examples:str(max=10, exclude='?!'): Allows only strings less than 11 characters that don't contain?or!.Regex -regex([patterns], name=string, ignore_case=False, multiline=False, dotall=False)Validates strings against one or more regular expressions.arguments: one or more Python regular expression patternskeywords:name: A friendly description for the patterns.ignore_case: Validates strings in a case-insensitive manner.multiline:^and$in a pattern match at the beginning and end of each line in a string
in addition to matching at the beginning and end of the entire string. (A pattern matches
atthe beginning of a stringeven in
multiline mode; see below for a workaround.)dotall:.in a pattern matches newline characters in a validated string in addition to
matching every character thatisn'ta newline.Examples:regex('^[^?!]{,10}$'): Allows only strings less than 11 characters that don't contain?or!.regex(r'^(\d+)(\s\1)+$', name='repeated natural'): Allows only strings that contain two or
more identical digit sequences, each separated by a whitespace character. Non-matching strings
likesugarare rejected with a message like'sugar' is not a repeated natural.regex('.*^apples$', multiline=True, dotall=True): Allows the stringapplesas well
as multiline strings that contain the lineapples.Integer -int(min=int, max=int)Validates integers.keywordsmin: int >= minmax: int <= maxNumber -num(min=float, max=float)Validates integers and floats.keywordsmin: num >= minmax: num <= maxBoolean -bool()Validates booleans.Null -null()Validates null values.Enum -enum([primitives])Validates from a list of constants.arguments: constants to test equality withExamples:enum('a string', 1, False): a value can be either'a string',1orFalseDay -day(min=date, max=date)Validates a date in the form of YYYY-MM-DD.keywordsmin: date >= minmax: date <= maxExamples:day(min='2001-01-01', max='2100-01-01'): Only allows dates between 2001-01-01 and 2100-01-01.Timestamp -timestamp(min=time, max=time)Validates a timestamp in the form of YYYY-MM-DD HH:MM:SS.keywordsmin: time >= minmax: time <= maxExamples:timestamp(min='2001-01-01 01:00:00', max='2100-01-01 23:00:00'): Only allows times between
2001-01-01 01:00:00 and 2100-01-01 23:00:00.List -list([validators], min=int, max=int)Validates lists. If one or more validators are passed tolist()only nodes that pass at
least one of those validators will be accepted.arguments: one or more validators to test values withkeywordsmin: len(list) >= minmax: len(list) <= maxExamples:list(): Validates any listlist(include('custom'), int(), min=4): Only validates lists that contain thecustominclude
or integers and contains a minimum of 4 items.Map -map([validators], key=validator, min=int, max=int)Validates maps. Use when you want a node to contain freeform data. Similar toList,Maptakes
one or more validators to run against the values of its nodes, and only nodes that pass at least
one of those validators will be accepted. By default, only the values of nodes are validated and
the keys aren't checked.arguments: one or more validators to test values withkeywordskey: A validator for the keys of the map.min: len(map) >= minmax: len(map) <= maxExamples:map(): Validates any mapmap(str(), int()): Only validates maps whose values are strings or integers.map(str(), key=int()): Only validates maps whose keys are integers and values are strings.1: onewould be valid but'1': onewould not.map(str(), min=1): Only validates a non-empty map.IP Address -ip()Validates IPv4 and IPv6 addresses.keywordsversion: 4 or 6; explicitly force IPv4 or IPv6 validationExamples:ip(): Allows any valid IPv4 or IPv6 addressip(version=4): Allows any valid IPv4 addressip(version=6): Allows any valid IPv6 addressMAC Address -mac()Validates MAC addresses.Examples:mac(): Allows any valid MAC addressAny -any([validators])Validates against a union of types. Use when a nodemustcontainone and only oneof several types. It is valid
if at least one of the listed validators is valid. If no validators are given, accept any value.arguments: validators to test values with (if none is given, allow any value; if one or more are given,
one must be present)Examples:any(int(), null()): Validates either an integerora null value.any(num(), include('vector')): Validateseithera numberoran included 'vector' type.any(str(min=3, max=3),str(min=5, max=5),str(min=7, max=7)): validates to a string that is exactly 3, 5, or 7 characters longany(): Allows any value.Subset -subset([validators], allow_empty=False)Validates against a subset of types. Unlike theAnyvalidator, this validators allowsone or moreof several types.
As such, itautomatically validates against a list. It is valid if all values can be validated against at least one
validator.arguments: validators to test with (at least one; if none is given, aValueErrorexception will be raised)keywords:allow_empty: Allow the subset to be empty (and is, therefore, also optional). This overrides therequiredflag.Examples:subset(int(), str()): Validators against an integer, a string, or a list of either.subset(int(), str(), allow_empty=True): Same as above, but allows the empty set and makes the subset optional.Include -include(include_name)Validates included structures. Must supply the name of a valid include.arguments: single name of a defined include, surrounded by quotes.Examples:include('person')Custom validatorsIt is also possible to add your own custom validators. This is an advanced topic, but here is an
example of adding aDatevalidator and using it in a schema asdate()importyamaleimportdatetimefromyamale.validatorsimportDefaultValidators,ValidatorclassDate(Validator):""" Custom Date validator """tag='date'def_is_valid(self,value):returnisinstance(value,datetime.date)validators=DefaultValidators.copy()# This is a dictionaryvalidators[Date.tag]=Dateschema=yamale.make_schema('./schema.yaml',validators=validators)# Then use `schema` as normalExamples:warning: Ensure that your schema definitions come from internal or trusted sources. Yamale does not protect against intentionally malicious schemas.Using keywordsSchema:optional:str(required=False)optional_min:int(min=1, required=False)min:num(min=1.5)max:int(max=100)Valid Data:optional_min:10min:1.6max:100Includes and recursionSchema:customerA:include('customer')customerB:include('customer')recursion:include('recurse')---customer:name:str()age:int()custom:include('custom_type')custom_type:integer:int()recurse:level:int()again:include('recurse', required=False)Valid Data:customerA:name:bobage:900custom:integer:1customerB:name:jillage:1custom:integer:3recursion:level:1again:level:2again:level:3again:level:4ListsSchema:list_with_two_types:list(str(), include('variant'))questions:list(include('question'))---variant:rsid:str()name:str()question:choices:list(include('choices'))questions:list(include('question'), required=False)choices:id:str()Valid Data:list_with_two_types:-'some'-rsid:'rs123'name:'someSNP'-'thing'-rsid:'rs312'name:'anotherSNP'questions:-choices:-id:'id_str'-id:'id_str1'questions:-choices:-id:'id_str'-id:'id_str1'The data is a list of items without a keyword at the top levelSchema:list(include('human'), min=2, max=2)---human:name:str()age:int(max=200)height:num()awesome:bool()Valid Data:-name:Billage:26height:6.2awesome:True-name:Adrianage:23height:6.3awesome:TrueWriting TestsTo validate YAML files when you run your program's tests use Yamale's YamaleTestCaseExample:classTestYaml(YamaleTestCase):base_dir=os.path.dirname(os.path.realpath(__file__))schema='schema.yaml'yaml='data.yaml'# or yaml = ['data-*.yaml', 'some_data.yaml']defrunTest(self):self.assertTrue(self.validate())base_dir: String path to prepend to all other paths. This is optional.schema: String of path to the schema file to use. One schema file per test case.yaml: String or list of yaml files to validate. Accepts globs.DevelopersTestingYamale usesToxto run its tests against multiple Python
versions. To run tests, first checkout Yamale, install Tox, then runmake testin Yamale's root
directory. You may also have to install the correct Python versions to test with as well.NOTE on Python versions:tox.inispecifies the lowest and highest versions of Python supported by
Yamale. Unless your development environment is configured to support testing against multiple Python
versions, one or more of the test branches may fail. One method of enabling testing against multiple
versions of Python is to installpyenvandtox-pyenvand to usepyenv installandpyenv localto ensure that tox is able to locate appropriate Pythons.ReleasingYamale uses Github Actions to upload new tags to PyPi.
To release a new version:Make a commit with the new version number inyamale/VERSION.Run tests for good luck.Runmake release.Github Actions will take care of the rest.