Coverage for sources/ictruck/__/validators.py: 100%

30 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-06 03:49 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Validators for internal use. ''' 

22 

23 

24from __future__ import annotations 

25 

26from . import imports as __ 

27 

28 

29def validate_arguments( 

30 globalvars: dict[ str, __.typx.Any ], 

31 errorclass: type[ Exception ], 

32): 

33 ''' Decorator factory which produces argument validators. ''' 

34 

35 def decorate( function: __.cabc.Callable[ ..., __.typx.Any ] ): 

36 ''' Decorates function to be validated. ''' 

37 

38 @__.funct.wraps( function ) 

39 def validate( *posargs: __.typx.Any, **nomargs: __.typx.Any ): 

40 ''' Validates arguments before invocation. ''' 

41 signature = __.inspect.signature( function ) 

42 inspectee = signature.bind( *posargs, **nomargs ) 

43 inspectee.apply_defaults( ) 

44 for name, value in inspectee.arguments.items( ): 

45 param = signature.parameters[ name ] 

46 annotation = param.annotation 

47 if __.is_absent( value ): continue 

48 if annotation is param.empty: continue 

49 classes = _reduce_annotation( 

50 annotation, globalvars = globalvars ) 

51 if not isinstance( value, classes ): 

52 raise errorclass( name, classes ) 

53 return function( *posargs, **nomargs ) 

54 

55 return validate 

56 

57 return decorate 

58 

59 

60def _reduce_annotation( 

61 annotation: __.typx.Any, globalvars: dict[ str, __.typx.Any ] 

62) -> tuple[ type, ... ]: 

63 if isinstance( annotation, str ): 

64 return _reduce_annotation( 

65 eval( annotation, globalvars ), # noqa: S307 

66 globalvars = globalvars ) 

67 origin = __.typx.get_origin( annotation ) 

68 if isinstance( annotation, __.types.UnionType ) or origin is __.typx.Union: 

69 return tuple( __.itert.chain.from_iterable( 

70 map( 

71 lambda a: _reduce_annotation( a, globalvars = globalvars ), 

72 __.typx.get_args( annotation ) ) ) ) 

73 if origin is None: return ( annotation, ) 

74 if origin is __.typx.Annotated: 

75 return _reduce_annotation( 

76 annotation.__origin__, globalvars = globalvars ) 

77 # TODO? Other special forms. 

78 return ( origin, )