Package: pynsure
pynsure[!TIP][pin-sure](like insure 😄)Predicate-based runtime constraint validator for PythonCheck it outJust use type hints and a decorator to verify parameter constraints at runtime.frompynsureimportvalidate,Unsigned@validate()defadd_two(a:Unsigned,b:int)->int:returna+badd_two(3,4)# OKadd_two(-2,4)# BADMake your own types usingtyping.Annotated!frompynsureimportvalidatefromtypingimportAnnotatedOdd=Annotated[int,(lambda_v:_v&1==1,"{}must be odd but its value is {{}}")]@validate()defadd_two(a:Odd,b:Odd):returna+badd_two(3,5)# OKadd_two(4,5)# BAD[!Note]{}is replaced with the parameter name and{{}}is replaced by the
parameter value automatically when aValidationErroris raised.You can even specify multiple predicates for your Annotated types:frompynsureimportvalidatefromtypingimportAnnotatedPositiveAndOdd=Annotated[int,(lambda_v:_v&1==1,"{}must be odd but its value is {{}}"),(lambda_v:_v>0,"{}must be greater than 0 but its value is {{}}"),]@validate()defadd_two(a:PositiveAndOdd,b:PositiveAndOdd)->int:returna+badd_two(7,9)# OKadd_two(-7,9)# BADadd_two(7,8)# BADOnly Annotated types following this form are validated:MyType = Annotated[<base_type>, (<predicate>, <message>), <...>]where...can be more predicate + message tuples"Why not use asserts?"Valid point.Basically,pynsuremakes it easier for users of your functions to understand
the exact constraints surrounding a parameter (or return value). You can make
and document your constraints and have them runtime-validated in just a few
easy steps.If my function expects four positive numbers, it's a whole lot easier to
annotate like this:@validate()defadd_four(a:Unsigned,b:Unsigned,c:Unsigned,d:Unsigned)->Unsigned:returna+b+c+d...than it is to do this:defadd_four(a,b,c,d):assertisinstance(a,int)anda>=0,"a should be greater than or equal to 0"assertisinstance(b,int)andb>=0,"b should be greater than or equal to 0"assertisinstance(c,int)andc>=0,"c should be greater than or equal to 0"assertisinstance(d,int)andd>=0,"d should be greater than or equal to 0"return...pluspynsurewill validate return types and return value constraints at
runtime too! ifadd_four()doesn't return an integer greater than or equal to
0, aValidationErrorwill be raised."Why not use pydantic"Great point.pydanticis awesome (and battle-tested) but is a bit clunky if you want to do
basic constraints and don't necessarily need giant serializable objects that
you can convert from json to a dict and a whole bunch of other fancy stuff.You have a function that expects non-empty strings?pynsuremakes this a
breeze. Let's compare:Using pynsurefrompynsureimportvalidatefromtypingimportAnnotatedNonEmptyStr=Annotated[str,(lambda_s:len(_s)>0,"{}shouldn't be empty")]@validate()defformat_name(first_name:NonEmptyStr,last_name:NonEmptyStr)->NonEmptyStr:returnf"{last_name},{first_name}"format_name(first_name="Bob",last_name="Smith")Using pydanticfrompydanticimportBaseModel,constrclassNonEmptyStr(BaseModel):string:constr(min_length=1)defformat_name(first_name:NonEmptyStr,last_name:NonEmptyStr)->str:returnf"{last_name},{first_name}"format_name(first_name=NonEmptyStr(string="Bob"),last_name=NonEmptyStr(string="Smith"))This is fine but a bit clunky having to fiddle around with BaseModels.ConclusionUltimately, use whatever you want, this isn't some new standard and you'll
probably get better mileage frompydantic(as far as runtime validation is
concerned). But, if you want to set some quick and easy constraints on your
methods, givepynsurea shot.