Package: art-deco
Art DecoDescriptionArt Deco is a library that, via decorators, makes complex processing of function/method arguments effortless.
Use cases forart_decoinclude, but are not limited to:data validation (e.g., validating the inputs of a function)input conversion (e.g., type coercion based on type annotations)deserializing / parsing (e.g., parse JSONs or decode MessagePack into Python objects based on type annotations)cachingThat is, this library shines when one needs to build a decorator that processes inputs based on:
theinspected signatureof the function arguments (e.g., needs access to argument names, type annotations,
default values), orruntime informationlike discerning whether an argument was called positionally or as a keyword,
as part of a variable argument (i.e.,*args,**kwargs) etc.Therefore, it works like a charm in conjunction with any data validation library (e.g.,cerberus,pandera) or any library that consumes data from the external world - network, Excel etc.
(e.g.,flask,plotly dash,pyxll,xlwings).Moreover,art_decohandles several Python language constructs:functions, regular methods, static methods, class methodssync andasyncmetaclasses and classes (regular classes,dataclasses,attrsclasses,pydanticmodels and data classes)variable arguments (i.e.,*args,**kwargs)positional only, keyword only argumentsand provides:art_deco.core: a fundamental layer based on which one can implement any argument processing logic.art_deco.hack_args: a convenient, pre-packaged argument processor implemented on top ofart_deco.core.Getting StartedInstallationpipinstallart_decoHacking function argumentsTo illustrate what can be achieved, let's start with a few toy usages ofart_deco.hack_args.from__future__importannotationsfromart_deco.hack_argsimportmarksfromart_deco.hack_args.processorimporthack_args@marks.arg_hackerdefhack_x(x:int|float|str)->str:"""Function used for data validation and type coercion."""assertfloat(x)<100,f'Validation failed:{x}must be < 100'returnstr(x)ifisinstance(x,(int,float))elsex@hack_args({'x':hack_x})deffunc(x):"""Function with a processed argument."""returnxassertfunc('1')==func(1)=='1'# func(200) ----> Errors with AssertionError, thanks to data validationthe same thing can be achieved via theAnnotatedtype hint:fromtyping_extensionsimportAnnotated@hack_args()deffunc(x:Annotated[str,hack_x])->str:"""Function with a processed argument."""returnxTo validate more than one argument at a time:fromart_deco.core.arg_processors.apiimportContext@marks.wide_validatordefvalidate_x_y(_:Context,x:str,y:str)->None:"""Validate 2 arguments."""# Context is an internal concept that allows users to use static information like the inspected signature or# default values, as well as call runtime information like what arguments were called positionally.ifx.startswith('1'):asserty.startswith('2'),f'Validation failed:{x}starts with 1, but{y}does not start with 2'@hack_args({'x':hack_x,'y':hack_x,('x','y'):validate_x_y})deffunc(x,y):"""Function with 2 processed arguments."""returnf'x={x}, y={y}'# Similarly we could use `Annotated`@hack_args()deffunc(x:Annotated[str,hack_x,validate_x_y],y:Annotated[str,hack_x,validate_x_y])->str:"""Function with 2 processed arguments."""returnf'x={x}, y={y}'Implementing a new argument processorInart_deco.core, there are 2 types of argument processors:static:initialized once, at function definition time, arguments to be processed are determined once as well,
while the actual argument processing happens every time the decorated function is called.dynamic:initialized every time the decorated function is called, arguments to be processed and the actual
argument processing happen every time the decorated functions is called.Let's look at a static example, since a dynamic processor would have a nearly identical interface.from__future__importannotationsfromtypingimportAny,Mappingfromart_deco.core.arg_processors.apiimportArgNames,Context,MultiArgValidateFuncfromart_deco.core.arg_processors.static_decoratorimportstatic_process_argsfromart_deco.core.specs.static_arg_specsimportStaticArgSpecsfromart_deco.core.specs.dynamic_arg_specsimportDynamicArgSpecclassSimpleCastingProcessor:def__init__(self,static_args:StaticArgSpecs)->None:self.static_args=static_argsself.hacks:dict[str,Any]={}forarginstatic_args.args:ifarg.has_annotation:self.hacks[arg.name]=arg.annotationdefshould_process_arg(self,arg:str)->bool:"""Hook function called to determine whether to process an argument or not, always by name."""returnarginself.hacksdefprocess_arg(self,arg:DynamicArgSpec,_:Context)->Any:"""This function specifies how to actually process the argument."""ifarg.is_default:# If the value is the default one, do not run any castingreturnarg.valuereturnself.hacks[arg.name](arg.value)# Get the annotation for the argument name and coerce the supplied valuedefget_wide_checks(self)->Mapping[ArgNames,MultiArgValidateFunc]:# No logic for "wide" checks.return{}@static_process_args(SimpleCastingProcessor)defcoercion(x:int,y:float)->float:returnx+yassertcoercion('1','2')==3assertcoercion('1',2)==3assertcoercion(1,2)==3DevelopmentReadCONTRIBUTING.md.