Package: dictparse
dictparseA simple, slim and useful, zero-dependency utility for parsing dictionaries or dictionary-like objects.It's particularly useful for parsing incoming request data in REST APIs & web applications, for example in the case
of Flask, parsing form data fromrequest.form, query string arguments fromrequest.argsor JSON data fromrequest.json.Thedictparsedesign takes inspiration from Python's ownargparselibrary, similar to theArgumentParserclass
, taking input as a dictionary or dictionary-like object, enforcing rules, types, applying functions, default values and
returning aNameSpace, with values mapped to attributes.InstallationpipinstalldictparseExampleThe following code is a Python program that takes takes some data in the form of a dictionary and parses it:>>>fromdictparseimportDictionaryParser>>>parser=DictionaryParser()>>>parser.add_param("name",str,required=True)>>>params=parser.parse_dict({"name":"FooBar"})>>>params.name'FooBar'Creating a parserThe first step is to create the DictionaryParser object>>>fromdictparseimportDictionaryParser>>>parser=DictionaryParser(description="Create a new user")Adding parametersAdding parameters to the parser is done by making calls to theadd_parammethod. These calls tell theDictionaryParserhow to handle the values passed in and turn them into the desired output, enforcing rules
, changing types and transforming values based on the arguments passed to theadd_parammethod.>>>parser=DictionaryParser()>>>parser.add_param("name",str,required=True)>>>parser.add_param("language",str,choices=["python","javascript","rust"])>>>parser.add_param("tags",str,action=lambdax:x.split(","))>>>params=parser.parse_dict({"name":"FooBar","language":"python","tags":"foo,bar,baz"})>>>params.name'FooBar'>>>params.language'python'>>>params.tags['foo', 'bar', 'baz']>>>params.to_dict(){'name': 'FooBar', 'language': 'python', 'tags': ['foo', 'bar', 'baz']}If the parser does not find a value matching the name, the default value isNoneArguments available foradd_paramDictionaryParser.add_param(name:str,type_:Optional[Union[Type[str],Type[int],Type[float],Type[bool],Type[list],Type[dict],Type[set],Type[tuple]]]=None,dest:Optional[str]=None,required:Optional[bool]=False,choices:Optional[Union[list,set,tuple]]=None,action:Optional[Callable]=None,description:Optional[str]=None,default:Optional[Any]=None,regex:Optional[str]=None)->Nonename: The parameter name (required - See note below)type_: The common parameter type (The parser will attempt to convert the parameter value to the given type)dest: The destination name of the parameter (See note below)required: IfTrue, enforce a value for the parameter must existschoices: A list, set, or tuple of possible choicesaction: A function to apply to the value (Applied after any type conversion)description: A description of the parameterdefault: A default value for the parameter if not foundregex: A regular expression to match against (Sets the parameter toNoneif the match is negative)Note - Thenameanddestparameters must comply with standard Python variable naming conventions (only start
with a letter or underscore & only contain alpha-numeric characters), not be a Python keyword and not start and end
with a double underscore (dunder)Parsing the dataAfter creating the parser and adding parameters to it, data can be parsed by calling theparse_dictmethod, passing
in the data to be parsed. This returns aNameSpaceobject.DictionaryParser.parse_dict(data:Dict[str,Any],strict:Optional[bool]=False,action:Optional[Callable]=None)->NameSpace:data: A dictionary or dictionary-like objectstrict: IfTrue, raises an exception if any parameters not added to the parser are receivedaction: A function to apply to all parameters (after any type conversion and after action passed toadd_param)TheNameSpaceobjectANameSpaceobject is returned when callingparse_dictand contains the parsed data after applying your rules
defined when adding arguments.Parameters can be accessed as attributes of theNameSpaceusing dot notation:>>>parser=DictionaryParser()>>>parser.add_param("age",int,required=True)>>>params=parser.parse_dict({"age":30})>>>params.age30A standardAttributeErrorwill be raised if an attribute is accessed without being added to the parser:>>>params.fooTraceback (most recent call last):File"<stdin>", line1, in<module>AttributeError:'NameSpace' object has no attribute 'foo'if thedestparameter is supplied when adding a parameter inadd_param, the value can only be accessed by using thedestvalue:>>>parser=DictionaryParser()>>>parser.add_param("bar",str,dest="foo")>>>params=parser.parse_dict({"bar":"bar"})>>>params.foo'bar'TheNameSpaceobject has the following available methods:getNameSpace.get(name:str,default:Optional[Any]=None)->Union[None,Any]:Calling thegetmethod on theNameSpaceand passing in a key works in the same way as callinggeton a dictionary
, returning either the value for the parameter requested orNoneif theNameSpacedoes not have that attribute.An optional default value can be supplied using thedefaultparameter to be returned if the attribute does not exist.>>>parser=DictionaryParser()>>>parser.add_param("age",int,required=True)>>>parser.add_param("weight",int)>>>params=parser.parse_dict({"age":30,"height":1.9})>>>params.weightNone>>>params.get("age")30>>>params.get("foo",42)42to_dictNameSpace.to_dict()->dictReturns a dictionary of the parsed parameters.>>>parser=DictionaryParser()>>>parser.add_param("one",str)>>>parser.add_param("two",int)>>>parser.add_param("three",list)>>>params=parser.parse_dict({"one":"one","two":2,"three":[1,2,3]})>>>params.to_dict(){'one': 'one', 'two': 2, 'three': [1, 2, 3]}to_dict()accepts an optional parameterexclude, a list of keys to exclude from the returned dictionary>>>fromdictparseimportDictionaryParser>>>parser=DictionaryParser()>>>parser.add_param("csrf_token",str,required=True)>>>parser.add_param("name",str)>>>parser.add_param("email",str)>>>params=parser.parse_dict({"csrf_token":"xxyyzz112233","name":"foo","email":"foo@bar.com"})>>>params.to_dict(exclude=["csrf_token"]){'name': 'foo', 'email': 'foo@bar.com'}get_paramReturns aParamobject>>>fromdictparseimportDictionaryParser>>>parser=DictionaryParser()>>>parser.add_param("names",list,default=[])>>>params=parser.parse_dict({"names":["foo","bar"]})>>>names=params.get_param("names")>>>names.name'names'>>>names.value['foo', 'bar']>>>names.default[]Paramobjects are hold all data associated with the parameter, as can be seen below in theParam.__init__method:classParam(object):def__init__(self,name:str,type_:Optional[Union[Type[str],Type[int],Type[float],Type[bool],Type[list],Type[dict],Type[set],Type[tuple]]]=None,dest:Optional[str]=None,required:Optional[bool]=False,choices:Optional[Union[list,set,tuple]]=None,action:Optional[Callable]=None,description:Optional[str]=None,default:Optional[Any]=None,regex:Optional[str]=None,value:Optional[Any]=None):Note - TheNameSpacewill be assigned the value fordestif supplied inadd_param>>>fromdictparseimportDictionaryParser>>>parser=DictionaryParser()>>>parser.add_param("foo",str,dest="bar")>>>params=parser.parse_dict({"foo":42})>>>param=params.get_param("bar")>>>param.name'foo'>>>param.dest'bar'>>>param.value'42'Flask exampleAn example of parsing JSON data sent in a POST request to a Flask route:fromapp.usersimportcreate_userfromflaskimportFlask,requestfromrespondimportJSONResponsefromdictparseimportDictionaryParserdefcreate_app():app=Flask(__name__)@app.route("/",methods=["POST"])defpost():parser=DictionaryParser(description="Create a new user")parser.add_param("name",str,required=True)parser.add_param("age",int)parser.add_param("password",str,required=True,action=lambdax:x.encode("utf-8"))parser.add_param("interests",list,action=lambdax:[i.strip()foriinx])parser.add_param("level",float,default=1.5)parser.add_param("stage",str,choices=["alpha","beta"])try:params=parser.parse_dict(request.get_json())exceptExceptionase:returnJSONResponse.bad_request(str(e))user=create_user(name=params.name,age=params.age,password=params.password,interests=params.interests,level=params.level,stage=params.stage)returnJSONResponse.created(user.to_dict())returnappif__name__=="__main__":app=create_app()app.run()Exception handlingExceptions will be raised in the following scenarios:ParserTypeErrorRaised when a parameter cannot be parsed to the type declared inadd_paramfromdictparseimportDictionaryParserfromdictparse.exceptionsimportParserTypeErrorparser=DictionaryParser()parser.add_param("age",int)try:params=parser.parse_dict({"age":"thirty"})exceptParserTypeErrorase:print(e)# Invalid value 'thirty' for parameter 'age', expected 'int' not 'str'ParserTypeErrorcontains the following attributes:param: The parameter name (str)value: The parameter value (Any)expected: The expected type (type)ParserRequiredParameterErrorRaised whenparse_dictis called and a parameter is required, but not foundfromdictparseimportDictionaryParserfromdictparse.exceptionsimportParserRequiredParameterErrorparser=DictionaryParser()parser.add_param("name",str)parser.add_param("email",str,required=True)try:params=parser.parse_dict({"name":"John Doe"})exceptParserRequiredParameterErrorase:print(e)# Missing required parameter 'email'ParserRequiredParameterErrorhas a single attributeparam, the name of the parameter (str)ParserInvalidChoiceErrorRaised whenparse_dictis called and parses a value not defined in thechoicesparameter ofadd_paramfromdictparseimportDictionaryParserfromdictparse.exceptionsimportParserInvalidChoiceErrorparser=DictionaryParser()parser.add_param("name",str)parser.add_param("language",str,choices=["python","bash"])try:params=parser.parse_dict({"name":"John Doe","language":"javascript"})exceptParserInvalidChoiceErrorase:print(e)# Parameter 'language' must be one of '['python', 'bash']', not 'javascript'ParserInvalidChoiceErrorhas the following 3 attributes:param: The parameter name (str)value: The parameter value (Any)choices: The available choices added viaadd_param(list|set|tuple)ParserInvalidParameterErrorRaised callingparse_dictwithstrictset toTrueThestrictparameter enforces the parser to only accept parameters that have been added to the parserfromdictparseimportDictionaryParserfromdictparse.exceptionsimportParserInvalidParameterErrorparser=DictionaryParser()parser.add_param("name",str)parser.add_param("language",str,choices=["python","bash"])try:params=parser.parse_dict({"name":"John Doe","language":"python","email":"jdoe@gmail.com"},strict=True)exceptParserInvalidParameterErrorase:print(e)# Invalid parameter 'email'ParserInvalidParameterErrorhas a single attributeparam, the name of the parameter (str)Other runtime considerations forparse_dictIf an invalid data type fordatais passed toparse_dict(such as a list or string), it raises aParserInvalidDataTypeErrorfromdictparseimportDictionaryParserfromdictparse.exceptionsimportParserInvalidDataTypeErrorparser=DictionaryParser()parser.add_param("name",str)try:params=parser.parse_dict([{"name","John Doe"}])exceptParserInvalidDataTypeErrorase:print(e)# Invalid type for 'data', must be a dict or dict-like object, not 'list'try:params=parser.parse_dict("foo")exceptParserInvalidDataTypeErrorase:print(e)# Invalid type for 'data', must be a dict or dict-like object, not 'str'Tests & coverageA test suite is available in thetestsdirectory with 100% coverage (15/Sep/2020)NameStmtsMissCover
---------------------------------------------
dictparse/__init__.py10100%
dictparse/exceptions.py370100%
dictparse/parser.py1060100%
tests/__init__.py00100%
tests/test_parser.py3100100%
---------------------------------------------
TOTAL4540100%