def runbuilder(s, attribs='', build=None, exclude=''): """ This test function takes a string containing the contents of a module. It writes the string contents to a file, imports the file as a module, and uses build_doc to build documentation, and pretty prints the resulting ModuleDoc object. The C{attribs} argument specifies which attributes of the C{APIDoc}s should be displayed. The C{build} argument gives the name of a variable in the module whose documentation should be built, instead of bilding docs for the whole module. """ # Write it to a temp file. file_path = write_pystring_to_tmp_dir(s) # Build it. val_doc = build_doc(file_path) if build: val_doc = val_doc.variables[build].value # Display it. if isinstance(val_doc, ClassDoc): for val in val_doc.variables.values(): if isinstance(val.value, RoutineDoc): fun_to_plain(val.value) s = val_doc.pp(include=attribs.split(),exclude=exclude.split()) s = re.sub(r"(filename = ).*", r"\1...", s) s = re.sub(r"(<module 'epydoc_test' from ).*", r'\1...', s) s = re.sub(r"(<function \w+ at )0x\w+>", r"\1...>", s) s = re.sub(r"(<\w+ object at )0x\w+>", r"\1...>", s) print(s) # Clean up. cleanup_tmp_dir(file_path)
def runbuilder(s, attribs='', build=None, exclude=''): """ This test function takes a string containing the contents of a module. It writes the string contents to a file, imports the file as a module, and uses build_doc to build documentation, and pretty prints the resulting ModuleDoc object. The C{attribs} argument specifies which attributes of the C{APIDoc}s should be displayed. The C{build} argument gives the name of a variable in the module whose documentation should be built, instead of bilding docs for the whole module. """ # Write it to a temp file. tmp_dir = write_pystring_to_tmp_dir(s) # Build it. val_doc = build_doc(os.path.join(tmp_dir, 'epydoc_test.py')) if build: val_doc = val_doc.variables[build].value # Display it. if isinstance(val_doc, ClassDoc): for val in val_doc.variables.values(): if isinstance(val.value, RoutineDoc): fun_to_plain(val.value) s = val_doc.pp(include=attribs.split(), exclude=exclude.split()) s = re.sub(r"(filename = ).*", r"\1...", s) s = re.sub(r"(<module 'epydoc_test' from ).*", r'\1...', s) s = re.sub(r"(<function \w+ at )0x\w+>", r"\1...>", s) s = re.sub(r"(<\w+ object at )0x\w+>", r"\1...>", s) print(s) # Clean up. cleanup_tmp_dir(tmp_dir)
def buildvaluedoc(s): """ This test function takes a string containing the contents of a module. It writes the string contents to a file, imports the file as a module, and uses build_doc to build documentation, and returns it as a C{ValueDoc} object. """ tmp_dir = write_pystring_to_tmp_dir(s) val_doc = build_doc(os.path.join(tmp_dir, 'epydoc_test.py')) cleanup_tmp_dir(tmp_dir) return val_doc
def buildvaluedoc(s): """ This test function takes a string containing the contents of a module. It writes the string contents to a file, imports the file as a module, and uses build_doc to build documentation, and returns it as a C{ValueDoc} object. """ file_path = write_pystring_to_tmp_dir(s) val_doc = build_doc(file_path) cleanup_tmp_dir(file_path) return val_doc
def getMethodTypedArgument(specFile): val_doc = parse_docs(specFile) doc = build_doc(specFile) moduledoc = introspect_docs(filename=specFile) methodDetails = dict() for ckey in moduledoc.variables.iterkeys(): classdoc = moduledoc.variables[ckey].value for rkey in classdoc.variables.iterkeys(): routinedoc = classdoc.variables[rkey].value if isinstance(routinedoc, RoutineDoc): argType=dict() for arg in routinedoc.arg_types.iterkeys(): argument = str(routinedoc.arg_types[arg]) paramType = re.findall('<epytext><para inline=True>(?P<paramtype>.*)</para></epytext>', argument)[0] argType[arg] = paramType methodDetails[rkey] = argType return methodDetails
def contract_epydoc(f): """ The decorator for any functions which have a epydoc-formatted docstring. It validates the function inputs and output against the contract defined by the epydoc description. Currently, it supports the usual functions as well as simple object methods (though neither classmethods nor staticmethods). Inside the epydoc contracts, it supports the following fields: - C{@type arg:} - the type of the C{arg} argument is validated before the function is called. - C{@rtype:} - the return type of the function is validated after the function is called. - C{@precondition:} - the precondition (that may involve the arguments of the function) that should be satisfied before the function is executed. - C{@postcondition:} - the postcondition (that may involve the result of the function given as C{result} variable) that should be satisfied after the function is executed. @param f: The function which epydoc documentation should be verified. @precondition: callable(f) """ if ENABLED: try: from epydoc import apidoc, docbuilder, markup except ImportError: raise ImportError( "To use contract_epydoc() function, " "you must have the epydoc module " "(often called python-epydoc) installed.\n" "For more details about epydoc installation, see " "http://epydoc.sourceforge.net/") # Given a method/function, get the module # where the function is defined. module = inspect.getmodule(f) _stack = inspect.stack()[1:] # The function/method marked with @contract_epydoc may be # either top-level in the module, # or defined inside some namespace, like a class or another function. base_function_path = _get_function_base_path_from_stack(_stack) # Now, analyze the epydoc comments, # and maybe cacke the documentation linker. if hasattr(module, "_dbc_ds_linker"): _dbc_ds_linker = module._dbc_ds_linker else: _dbc_ds_linker = markup.DocstringLinker() if USE_EPYDOC_CACHE: module._dbc_ds_linker = _dbc_ds_linker contract = docbuilder.build_doc(f) preconditions = [ description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == "Precondition" ] postconditions = [ description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == "Postcondition" ] if isinstance(f, (staticmethod, classmethod)): raise NotImplementedError( "The @contract_epydoc decorator is not supported " "for either staticmethod or classmethod functions; " "please use it before (below) turning a function into " "a static method or a class method.") elif isinstance(contract, apidoc.RoutineDoc): f_path = "%(mod_name)s module (%(mod_file_path)s), %(func_name)s()" % { "mod_name": module.__name__, "mod_file_path": contract.defining_module.filename, "func_name": ".".join(filter(None, (base_function_path, f.__name__))), } else: raise Exception("@contract_epydoc decorator is not yet supported " "for %s types!" % type(contract)) _stack = inspect.stack() def_frame = _stack[1][0] # Don't copy the dictionaries, but refer to the original stack frame def_globals = def_frame.f_globals def_locals = def_frame.f_locals # # At this stage we have "f_path" variable containing # the fully qualified name of the called function. # Also, def_globals and def_locals contain the globals/locals of # the code where the decorated function was defined. # @wraps(f) def wrapped_f(*args, **kwargs): _stack = inspect.stack() # Do we actually want to use the globals with NoneType # already imported? #def_globals_with_nonetype = dict(def_globals); #def_globals_with_nonetype["NoneType"] = NoneType # Stack now: # [0] is the level inside wrapped_f. # [1] is the caller. # For "globals" dictionary, we should use the globals of the code # that called the wrapper function. call_frame = _stack[1][0] call_globals = call_frame.f_globals call_locals = call_frame.f_locals arguments_to_validate = list(contract.arg_types) try: expected_types = \ dict((argument, _parse_str_to_type( f_path, _preprocess_field_argument( contract.arg_types[argument].to_plaintext( _dbc_ds_linker)), "'%s' argument" % argument, _globals=def_globals, _locals=def_locals)) for argument in arguments_to_validate) except Exception, e: raise e # All values: # First try to use the default values; # then add the positional arguments, # then add the named arguments. values = dict( chain( izip(contract.posargs, ((df.pyval if df is not None else None) for df in contract.posarg_defaults)), izip(contract.posargs, args), kwargs.iteritems())) # Validate arguments for argument in arguments_to_validate: assert argument in values, "%r not in %r" % (argument, values) value = values[argument] expected_type = expected_types[argument] if not isinstance(value, expected_type): raise TypeError( "%s:\n" "The '%s' argument is of %r while must be of %r; " "its value is %r" % (f_path, argument, type(value), expected_type, value)) # Validate preconditions. # Preconditions may use the globals from the function definition, # as well as the function arguments. locals_for_preconditions = values for description_str in preconditions: description_str = _preprocess_field_argument(description_str) value = _parse_str_to_value(f_path, description_str, 'precondition definition', _globals=def_globals, _locals=locals_for_preconditions) if not value: raise ValueError( "%s:\n" "The following precondition results in logical False; " "its definition is:\n" "\t%s\n" "and its real value is %r" % (f_path, description_str.strip(), value)) # # Call the desired function # result = f(*args, **kwargs) # IGNORE THIS LINE # Validate return value if contract.return_type is not None: expected_type = _parse_str_to_type( f_path, _preprocess_field_argument( contract.return_type.to_plaintext(_dbc_ds_linker)), 'return value', _globals=def_globals, _locals=values) if not isinstance(result, expected_type): raise TypeError( "%s:\n" "The following return value is of %r while must be of %r: " "%r" % (f_path, type(result), expected_type, result)) # Validate postconditions. # Postconditions may use the globals from the function definition, # as well as the function arguments and the special "result" parameter. locals_for_postconditions = dict(locals_for_preconditions) locals_for_postconditions['result'] = result for description_str in postconditions: description_str = _preprocess_field_argument(description_str) value = _parse_str_to_value(f_path, description_str, 'postcondition definition', _globals=def_globals, _locals=locals_for_postconditions) if not value: raise ValueError( "%s:\n" "The following postcondition results in logical False; " "its definition is:\n" "\t%s\n" "and its real value is %r" % (f_path, description_str.strip(), value)) # Validations are successful return result return wrapped_f
def contract_epydoc(f): """ The decorator for any functions which have a epydoc-formatted docstring. It validates the function inputs and output against the contract defined by the epydoc description. Currently, it supports the usual functions as well as simple object methods (though neither classmethods nor staticmethods). Inside the epydoc contracts, it supports the following fields: - C{@type arg:} - the type of the C{arg} argument is validated before the function is called. - C{@rtype:} - the return type of the function is validated after the function is called. - C{@precondition:} - the precondition (that may involve the arguments of the function) that should be satisfied before the function is executed. - C{@postcondition:} - the postcondition (that may involve the result of the function given as C{result} variable) that should be satisfied after the function is executed. @param f: The function which epydoc documentation should be verified. @precondition: callable(f) """ if ENABLED: try: from epydoc import apidoc, docbuilder, markup except ImportError: raise ImportError( "To use contract_epydoc() function, " "you must have the epydoc module " "(often called python-epydoc) installed.\n" "For more details about epydoc installation, see " "http://epydoc.sourceforge.net/") # Given a method/function, get the module # where the function is defined. module = inspect.getmodule(f) _stack = inspect.stack()[1:] # The function/method marked with @contract_epydoc may be # either top-level in the module, # or defined inside some namespace, like a class or another function. base_function_path = _get_function_base_path_from_stack(_stack) # Now, analyze the epydoc comments, # and maybe cacke the documentation linker. if hasattr(module, "_dbc_ds_linker"): _dbc_ds_linker = module._dbc_ds_linker else: _dbc_ds_linker = markup.DocstringLinker() if USE_EPYDOC_CACHE: module._dbc_ds_linker = _dbc_ds_linker contract = docbuilder.build_doc(f) preconditions = [description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == "Precondition"] postconditions = [description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == "Postcondition"] if isinstance(f, (staticmethod, classmethod)): raise NotImplementedError( "The @contract_epydoc decorator is not supported " "for either staticmethod or classmethod functions; " "please use it before (below) turning a function into " "a static method or a class method.") elif isinstance(contract, apidoc.RoutineDoc): f_path = "%(mod_name)s module (%(mod_file_path)s), %(func_name)s()" % { "mod_name" : module.__name__, "mod_file_path" : contract.defining_module.filename, "func_name" : ".".join(filter(None, (base_function_path, f.__name__))), } else: raise Exception("@contract_epydoc decorator is not yet supported " "for %s types!" % type(contract)) _stack = inspect.stack() def_frame = _stack[1][0] # Don't copy the dictionaries, but refer to the original stack frame def_globals = def_frame.f_globals def_locals = def_frame.f_locals # # At this stage we have "f_path" variable containing # the fully qualified name of the called function. # Also, def_globals and def_locals contain the globals/locals of # the code where the decorated function was defined. # @wraps(f) def wrapped_f(*args, **kwargs): _stack = inspect.stack() # Do we actually want to use the globals with NoneType # already imported? #def_globals_with_nonetype = dict(def_globals); #def_globals_with_nonetype["NoneType"] = NoneType # Stack now: # [0] is the level inside wrapped_f. # [1] is the caller. # For "globals" dictionary, we should use the globals of the code # that called the wrapper function. call_frame = _stack[1][0] call_globals = call_frame.f_globals call_locals = call_frame.f_locals arguments_to_validate = list(contract.arg_types) try: expected_types = \ dict((argument, _parse_str_to_type( f_path, _preprocess_field_argument( contract.arg_types[argument].to_plaintext( _dbc_ds_linker)), "'%s' argument" % argument, _globals=def_globals, _locals=def_locals)) for argument in arguments_to_validate) except Exception, e: raise e # All values: # First try to use the default values; # then add the positional arguments, # then add the named arguments. values = dict(chain(izip(contract.posargs, ((df.pyval if df is not None else None) for df in contract.posarg_defaults)), izip(contract.posargs, args), kwargs.iteritems())) # Validate arguments for argument in arguments_to_validate: assert argument in values, "%r not in %r" % (argument, values) value = values[argument] expected_type = expected_types[argument] if not isinstance(value, expected_type): raise TypeError("%s:\n" "The '%s' argument is of %r while must be of %r; " "its value is %r" % (f_path, argument, type(value), expected_type, value)) # Validate preconditions. # Preconditions may use the globals from the function definition, # as well as the function arguments. locals_for_preconditions = values for description_str in preconditions: description_str = _preprocess_field_argument(description_str) value = _parse_str_to_value(f_path, description_str, 'precondition definition', _globals=def_globals, _locals=locals_for_preconditions) if not value: raise ValueError("%s:\n" "The following precondition results in logical False; " "its definition is:\n" "\t%s\n" "and its real value is %r" % (f_path, description_str.strip(), value)) # # Call the desired function # result = f(*args, **kwargs) # IGNORE THIS LINE # Validate return value if contract.return_type is not None: expected_type = _parse_str_to_type( f_path, _preprocess_field_argument( contract.return_type.to_plaintext( _dbc_ds_linker)), 'return value', _globals=def_globals, _locals=values) if not isinstance(result, expected_type): raise TypeError("%s:\n" "The following return value is of %r while must be of %r: " "%r" % (f_path, type(result), expected_type, result)) # Validate postconditions. # Postconditions may use the globals from the function definition, # as well as the function arguments and the special "result" parameter. locals_for_postconditions = dict(locals_for_preconditions) locals_for_postconditions['result'] = result for description_str in postconditions: description_str = _preprocess_field_argument(description_str) value = _parse_str_to_value(f_path, description_str, 'postcondition definition', _globals=def_globals, _locals=locals_for_postconditions) if not value: raise ValueError("%s:\n" "The following postcondition results in logical False; " "its definition is:\n" "\t%s\n" "and its real value is %r" % (f_path, description_str.strip(), value)) # Validations are successful return result return wrapped_f
def contract_epydoc(f): """ The decorator for any functions which have a epydoc-formatted docstring. It validates the function inputs and output against the contract defined by the epydoc description. Currently, it supports the usual functions as well as simple object methods (though neither classmethods nor staticmethods). Inside the epydoc contracts, it supports the following fields: - C{@type arg:} - the type of the C{arg} argument is validated before the function is called. - C{@rtype:} - the return type of the function is validated after the function is called. - C{@precondition:} - the precondition (that may involve the arguments of the function) that should be satisfied before the function is executed. - C{@postcondition:} - the postcondition (that may involve the result of the function given as C{result} variable) that should be satisfied after the function is executed. @param f: The function which epydoc documentation should be verified. @precondition: callable(f) """ if ENABLED: try: from epydoc import apidoc, docbuilder, markup except ImportError: raise ImportError( 'To use contract_epydoc() function, ' 'you must have the epydoc module (often called python-epydoc) installed.\n' 'For more details about epydoc installation, see http://epydoc.sourceforge.net/' ) # Given a method/function, get the module where the function is defined. module = inspect.getmodule(f) _stack = inspect.stack()[1:] # The function/method marked with @contract_epydoc may be either top-level in the module, # or defined inside some namespace, like a class or another function. base_function_path = _get_function_base_path_from_stack(_stack) # Now, analyze the epydoc comments, # and maybe cacke the documentation linker. if hasattr(module, '_dbc_ds_linker'): _dbc_ds_linker = module._dbc_ds_linker else: _dbc_ds_linker = markup.DocstringLinker() if USE_EPYDOC_CACHE: module._dbc_ds_linker = _dbc_ds_linker # Parse function contract contract = docbuilder.build_doc(f) preconditions = (description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == 'Precondition') postconditions = (description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == 'Postcondition') requirements = (description.to_plaintext(_dbc_ds_linker) for field, argument, description in contract.metadata if field.singular == 'Requires') if False: # TODO # Parse module contract (if available). extra_mod_imports = {} _module = contract.defining_module.pyval # Now find whole-module requirements # Is the set of extra imports cached in the module? if hasattr(_module, '_dbc_extra_locals'): # cached extra_mod_imports = _module._dbc_extra_locals else: # not cached # Let's parse it by epydoc, and cache try: mod_contract = docbuilder.build_doc(_module) except AttributeError as e: if e.message == b"_Sentinel instance has no attribute '__getitem__'": # No fields in docstring, or even no docstring at all. pass else: raise else: mod_requirements = ( description.to_plaintext(_dbc_ds_linker) for field, argument, description in mod_contract.metadata if field.singular == 'Requires') for r in mod_requirements: exec r in {}, extra_mod_imports _module._dbc_extra_locals = extra_mod_imports # cache it in the whole module del _module # "extra_mod_imports" now contain the additional modules to use. if isinstance(f, (staticmethod, classmethod)): raise NotImplementedError( 'The @contract_epydoc decorator is not supported ' 'for either staticmethod or classmethod functions; ' 'please use it before (below) turning a function into ' 'a static method or a class method.') elif isinstance(contract, apidoc.RoutineDoc): f_path = '%(mod_name)s module (%(mod_file_path)s), %(func_name)s()' % { 'mod_name': module.__name__, 'mod_file_path': contract.defining_module.filename, 'func_name': '.'.join(filter(None, (base_function_path, f.__name__))) } else: raise Exception( '@contract_epydoc decorator is not yet supported for %s types!' % type(contract)) _stack = inspect.stack() def_frame = _stack[1][0] del _stack # Don't copy the dictionaries, but refer to the original stack frame def_globals = def_frame.f_globals def_locals = def_frame.f_locals del def_frame # Take some data from contract arguments_to_validate = list(contract.arg_types) # # At this stage we have "f_path" variable containing the fully qualified name # of the called function. # # Also, "def_globals" and "def_locals" contain the globals/locals of the code # where the decorated function was defined. # # Other available variables are: # * arguments_to_validate # * contract (TODO: is it possible to avoid keeping it in memory?) # * _dbc_ds_linker (TODO: is it possible to avoid keeping it in memory?) @wraps(f) def wrapped_f(*args, **kwargs): _stack = inspect.stack() # Do we actually want to use the globals with NoneType already imported? #def_globals_with_nonetype = dict(def_globals); def_globals_with_nonetype["NoneType"] = NoneType # Stack now: # [0] is the level inside wrapped_f. # [1] is the caller. # For "globals" dictionary, we should use the globals of the code # that called the wrapper function. call_frame = _stack[1][0] call_globals = call_frame.f_globals call_locals = call_frame.f_locals try: expected_types = dict( (argument, _parse_str_to_type(f_path, contract.arg_types[argument]. to_plaintext(_dbc_ds_linker), "'%s' argument" % argument, _globals=def_globals, _locals=def_locals)) for argument in arguments_to_validate) except Exception, e: raise e # All values: # First try to use the default values; # then add the positional arguments, # then add the named arguments. values = dict( chain( izip(contract.posargs, ((df.pyval if df is not None else None) for df in contract.posarg_defaults)), izip(contract.posargs, args), kwargs.iteritems())) # Validate arguments for argument in arguments_to_validate: assert argument in values, '%r not in %r' % (argument, values) value = values[argument] expected_type = expected_types[argument] if not isinstance(value, expected_type): raise TypeError( '%s:\n' "The '%s' argument is of %r while must be of %r; " 'its value is %r' % (f_path, argument, type(value), expected_type, value)) # Validate preconditions. # Preconditions may use the globals from the function definition, # as well as the function arguments. locals_for_preconditions = values for description_str in preconditions: value = _parse_str_to_value(f_path, description_str, 'precondition definition', _globals=def_globals, _locals=locals_for_preconditions) if not value: raise ValueError( '%s:\n' 'The following precondition results in logical False; ' 'its definition is:\n' '\t%s\n' 'and its real value is %r' % (f_path, description_str.strip(), value)) # # Call the desired function # result = f(*args, **kwargs) # IGNORE THIS LINE # Validate return value if contract.return_type is not None: expected_type = _parse_str_to_type( f_path, contract.return_type.to_plaintext(_dbc_ds_linker), 'return value', _globals=def_globals, _locals=values) if not isinstance(result, expected_type): raise TypeError( '%s:\n' 'The following return value is of %r while must be of %r: ' '%r' % (f_path, type(result), expected_type, result)) # Validate postconditions. # Postconditions may use the globals from the function definition, # as well as the function arguments and the special "result" parameter. locals_for_postconditions = dict(locals_for_preconditions) locals_for_postconditions['result'] = result for description_str in postconditions: value = _parse_str_to_value(f_path, description_str, 'postcondition definition', _globals=def_globals, _locals=locals_for_postconditions) if not value: raise ValueError( '%s:\n' 'The following postcondition results in logical False; ' 'its definition is:\n' '\t%s\n' 'and its real value is %r' % (f_path, description_str.strip(), value)) # Validations are successful return result return wrapped_f