def resolve_signature( fn: Callable ) -> Tuple[Optional[inspect.Signature], Optional[ConditionSyntaxMessage]]: ''' Get signature and resolve type annotations with get_type_hints. Returns a pair of Nones if no signature is available for the function. (e.g. it's implemented in C) Returns an unresolved signature and an error message if the type resultion errors. (e.g. the annotation references a type name that isn't dfined) ''' # TODO: Test resolution with members at multiple places in the hierarchy. # e.g. https://bugs.python.org/issue29966 try: sig = inspect.signature(fn) except ValueError: return (None, None) try: type_hints = get_type_hints(fn, fn_globals(fn)) except NameError as name_error: filename, lineno = source_position(fn) return (sig, ConditionSyntaxMessage(filename, lineno, str(name_error))) params = sig.parameters.values() newparams = [] for name, param in sig.parameters.items(): if name in type_hints: param = param.replace(annotation=type_hints[name]) newparams.append(param) newreturn = type_hints.get('return', sig.return_annotation) return (inspect.Signature(newparams, return_annotation=newreturn), None)
def get_fn_conditions( self, fn: Callable, first_arg_type: Optional[type] = None) -> Optional[Conditions]: filename, first_line = source_position(fn) sig, resolution_err = resolve_signature(fn) if sig is None: return None if resolution_err: return Conditions(fn, [], [], frozenset(), sig, None, [resolution_err]) if first_arg_type: sig = set_first_arg_type(sig, first_arg_type) if isinstance(fn, types.BuiltinFunctionType): return Conditions(fn, [], [], frozenset(), sig, frozenset(), []) lines = list(get_doc_lines(fn)) parse = parse_sections(lines, ('pre', 'post', 'raises'), filename) pre: List[ConditionExpr] = [] raises: Set[Type[BaseException]] = set() post_conditions: List[ConditionExpr] = [] mutable_args: Optional[FrozenSet[str]] = None if parse.mutable_expr is not None: mutable_args = frozenset(expr.strip().split('.')[0] for expr in parse.mutable_expr.split(',') if expr != '') for line_num, expr in parse.sections['pre']: pre.append( condition_from_source_text(filename, line_num, expr, fn_globals(fn))) for line_num, expr in parse.sections['raises']: if '#' in expr: expr = expr.split('#')[0] for exc_source in expr.split(','): try: exc_type = eval(exc_source) except: e = sys.exc_info()[1] parse.syntax_messages.append( ConditionSyntaxMessage(filename, line_num, str(e))) continue if not issubclass(exc_type, BaseException): parse.syntax_messages.append( ConditionSyntaxMessage( filename, line_num, f'"{exc_type}" is not an exception class')) continue raises.add(exc_type) for line_num, expr in parse.sections['post']: post_conditions.append( condition_from_source_text(filename, line_num, expr, fn_globals(fn))) return Conditions(fn, pre, post_conditions, frozenset(raises), sig, mutable_args, parse.syntax_messages)
def get_class_invariants( self, cls: type, super_conditions: ClassConditions) -> List[ConditionExpr]: try: filename = inspect.getsourcefile(cls) except TypeError: # raises TypeError for builtins filename = None invariants = getattr(cls, '__invariants__', ()) # type: ignore ret = [] def inv_eval(contract, kwargs): return contract.condition(self=kwargs['self']) for contract in invariants: filename, line_num = source_position(contract.condition) ret.append( ConditionExpr(functools.partial(inv_eval, contract), filename, line_num, self.contract_text(contract))) return ret
def get_fn_conditions( self, fn: Callable, first_arg_type: Optional[type] = None) -> Optional[Conditions]: icontract = self.icontract checker = icontract._checkers.find_checker(func=fn) # type: ignore if checker is None: return None contractless_fn = fn.__wrapped__ # type: ignore sig, resolution_err = resolve_signature(fn) if sig is None: return None if resolution_err: return Conditions(contractless_fn, [], [], frozenset(), sig, None, [resolution_err]) if first_arg_type: sig = set_first_arg_type(sig, first_arg_type) pre: List[ConditionExpr] = [] post: List[ConditionExpr] = [] def eval_contract(contract, kwargs): condition_kwargs = icontract._checkers.select_condition_kwargs( contract=contract, resolved_kwargs=kwargs) return contract.condition(**condition_kwargs) disjunction = checker.__preconditions__ if len(disjunction) == 0: pass elif len(disjunction) == 1: for contract in disjunction[0]: evalfn = functools.partial(eval_contract, contract) filename, line_num = source_position(contract.condition) pre.append( ConditionExpr(evalfn, filename, line_num, self.contract_text(contract))) else: def eval_disjunction(disjunction, kwargs) -> bool: for conjunction in disjunction: ok = True for contract in conjunction: if not eval_contract(contract, kwargs): ok = False break if ok: return True return False evalfn = functools.partial(eval_disjunction, disjunction) filename, line_num = source_position(contractless_fn) source = '(' + ') or ('.join([ ' and '.join([self.contract_text(c) for c in conj]) for conj in disjunction ]) + ')' pre.append(ConditionExpr(evalfn, filename, line_num, source)) # TODO handle snapshots #snapshots = checker.__postcondition_snapshots__ # type: ignore def post_eval(contract, kwargs): _old = kwargs.pop('__old__') kwargs['result'] = kwargs.pop('__return__') del kwargs['_'] condition_kwargs = icontract._checkers.select_condition_kwargs( contract=contract, resolved_kwargs=kwargs) return contract.condition(**condition_kwargs) for postcondition in checker.__postconditions__: evalfn = functools.partial(post_eval, postcondition) filename, line_num = source_position(postcondition.condition) post.append( ConditionExpr(evalfn, filename, line_num, self.contract_text(postcondition))) return Conditions( contractless_fn, pre, post, raises=frozenset(( AttributeError, IndexError, )), # TODO all exceptions are OK? sig=sig, mutable_args=None, fn_syntax_messages=[])