def autoimport(noclobber=True, skip=None, calling_mod_name=None, calling_mod_path=None, level=2): """This function imports some pytest-helper and pytest attributes into the calling module's global namespace. This avoids having to explicitly do common imports. Even if `autoimport` is called from inside a test function it will still place its imports in the module's global namespace. A `PytestHelperException` will be raised if any of those globals already exist, unless `noclobber` is set false. The `imports` option is a list of (name,value) pairs to import automatically. Since using it each time would be as much trouble as doing the imports, explicitly, it is mainly meant to be set in configuration files. In a config file the values are evaluated in the global namespace of `pytest_helper`. The `skip` option is a list of names to skip in importing, if just one or two are causing problems locally to a file. The default variables that are imported from the `pytest_helper` module are `locals_to_globals`, `clear_locals_from_globals`, and `unindent`. The module `pytest` is imported as `pytest`. The functions from pytest that are imported by default are `raises`, `fail`, `fixture`, and `skip`, `xfail`, and `approx`.""" mod_info = get_calling_module_info(module_name=calling_mod_name, module_path=calling_mod_path, level=level) calling_mod_name, calling_mod, calling_mod_path, calling_mod_dir, in_pkg = mod_info # Override arguments with any values set in the config file. noclobber = get_config_value("autoimport_noclobber", noclobber, calling_mod, calling_mod_dir) skip = get_config_value("autoimport_skip", skip, calling_mod, calling_mod_dir) def insert_in_dict(d, name, value, noclobber): """Insert (name, value) in dict d checking for noclobber.""" if noclobber and name in d: raise PytestHelperException( "The pytest_helper function autoimport" "\nattempted an overwrite with noclobber set. The attribute" " is: " + name) try: d[name] = value except KeyError: raise g = get_calling_fun_globals_dict(level=level) for name, value in autoimport_DEFAULTS: if skip and name in skip: continue insert_in_dict(g, name, value, noclobber)
def autoimport(noclobber=True, skip=None, calling_mod_name=None, calling_mod_path=None, level=2): """This function imports some pytest-helper and pytest attributes into the calling module's global namespace. This avoids having to explicitly do common imports. A `PytestHelperException` will be raised if any of those globals already exist, unless `noclobber` is set false. The `imports` option is a list of (name,value) pairs to import automatically. Since using it each time would be as much trouble as doing the imports, explicitly, it is mainly meant to be set in configuration files. In a config file the values are evaluated in the global namespace of `pytest_helper`. The `skip` option is a list of names to skip in importing, if just one or two are causing problems locally to a file. The default variables that are imported from the `pytest_helper` module are `locals_to_globals`, and `clear_locals_from_globals`. The module `py.test` is imported as the single name `pytest`. The functions from pytest that are imported by default are `raises`, `fail`, `fixture`, and `skip`, and `xfail`.""" mod_info = get_calling_module_info(module_name=calling_mod_name, module_path=calling_mod_path, level=level) calling_mod_name, calling_mod, calling_mod_path, calling_mod_dir = mod_info # Override arguments with any values set in the config file. noclobber = get_config_value("autoimport_noclobber", noclobber, calling_mod, calling_mod_dir) skip = get_config_value("autoimport_skip", skip, calling_mod, calling_mod_dir) def insert_in_dict(d, name, value, noclobber): """Insert (name, value) in dict d checking for noclobber.""" if noclobber and name in d: raise PytestHelperException("The pytest_helper function autoimport" "\nattempted an overwrite with noclobber set. The attribute" " is: " + name) try: d[name] = value except KeyError: raise g = get_calling_fun_globals_dict(level=level) for name, value in autoimport_DEFAULTS: if skip and name in skip: continue insert_in_dict(g, name, value, noclobber) return
def init(set_package=False, conf=True, calling_mod_name=None, calling_mod_path=None, level=2): """A function to initialize the `pytest_helper` module just after importing it. This function is currently only necessary in rare cases where Python's current working directory (CWD) is changed between the time when the executing module is first loaded and when `script_run` or `sys_path` is called from that module. In those cases the module's pathname relative to the previous CWD will be incorrectly expanded relative to the new CWD. Calling this function causes the earlier expanded pathname to be cached. This function should be called before any function call or import which changes the CWD and which doesn't change it back afterward. Importing `pytest_helper` just after the system imports and then immediately calling this function should work. The `init` function takes an optional keyword argument `set_package`. If it is true then whenever the calling module is a script the package attribute of module `__main__` will be automatically set. This allows for using explicit relative imports from scripts, but it must be called before the explicit relative imports are attempted. If the calling module was run as a script then the function call `pytest_helper.init(set_package=True)` will import the `set_package_attribute` package and initialize it. Otherwise, it will do the same as the ordinary call. If the parameter `conf` is set false then no configuration files will be searched for or used. Otherwise, the configuration file will be searched for by any function which has an option settable in a config file (including the `init` function itself). """ # The get_calling_module_info function caches the module info, including the # calling_mod_path, as a side effect. mod_info = get_calling_module_info(module_name=calling_mod_name, module_path=calling_mod_path, level=level) calling_mod_name, calling_mod, calling_mod_path, calling_mod_dir = mod_info # Disable the configuration file if requested. if not conf or not ALLOW_USER_CONFIG_FILES: get_config(calling_mod, calling_mod_dir, disable=True) # Override init arguments with any values set in the config file. set_package = get_config_value("init_set_package", set_package, calling_mod, calling_mod_dir) # Handle the set_package option. if set_package: if calling_mod_name == "__main__": import set_package_attribute set_package_attribute.init() return
def script_run(testfile_paths=None, self_test=False, pytest_args=None, pyargs=False, modify_syspath=False, calling_mod_name=None, calling_mod_path=None, exit=True, always_run=False, level=2): """Run pytest on the specified test files when the calling module is run as a script. Using this function requires at least pytest 2.0. The argument `testfile_paths` should be either the pathname of a file or directory to run pytest on, or else a list of such file and directory paths. Any relative paths will be interpreted relative to the directory of the module which calls this function. The calculation of relative paths can fail in cases where Python's CWD is changed between the time when the calling module is loaded and a pytest-helper function is called. (Most programs do not change the CWD like that, or if they do they return it to its previous value.) In those cases the `pytest_helper.init()` function can be called just after importing `pytest_helper` (or absolute pathnames can be used). The recommended use of `script_run` is to place it inside a guard conditional which runs only for scripts, and to call it before doing any other non-system imports. The early call avoids possible problems with relative imports when running it from inside modules that are part of packages. The use of the guard conditional is optional, but is more explicit and slightly more efficient. If `self_test` is `True` then pytest will be run on the file of the calling script itself, i.e., tests are assumed to be in the same file as the code to test. The `pytest_args` allows command-line arguments to be passed to pytest when it is run. It can be set to a string of all the options or else a list of strings (with one item in the list for each flag and for each flag argument). Options containing non-separator spaces, such as whitespace in quoted filenames, are currently not allowed in the string form. In that case the list form should be used. The pytest command-line argument `--pyargs` allows a mix of filenames and Python package names to be passed to pytest as test files. Note that pytest *always* imports modules as part of a package if there is an `__init__.py` file in the directory; the `--pyargs` just allows Python-style module names. When `pyargs` is set true pytest will be run with the `--pyargs` option set, and any items in `testfile_paths` which contain no path-separator character (slash) will be left unprocessed rather than being converted into absolute pathnames. The pytest option `--pyargs` will not work correctly unless this flag is set. The default is `pyargs=False`, i.e., by default all paths are converted to absolute pathnames. It usually will not matter, but in this mode you can specify a directory name relative to the current directory and not have it treated as a Python module name by using `./dirname` rather than simply `filename`. If `modify_syspath` is set true (the default is false) then the first item in the `sys.path` list is deleted just after `script_run` is called. When a module is run as a script Python always adds the directory of the script as the first item in `sys.path`. This can sometimes cause hard-to-trace import errors when directories inside paths are inserted in `sys.path`. This flag should seldom be needed because pytest seems to already take care of this. The `calling_mod_name` argument is a fallback in case the calling function's module is not correctly located by introspection. It is usually not required (though it is slightly more efficient). Use it as: `module_name=__name__`. Similarly for `calling_mod_path`, but that should be passed the pathname of the calling module's file. If `exit` is set false `sys.exit(0)` will not be called after the tests finish. The default is to exit after the tests finish (otherwise when tests run from the top of a module are finished the rest of the file will still be executed). Setting `exit` false can be used to make several separate `script_run` calls in sequence. If `always_run` is true then tests will be run regardless of whether or not the function was called from a script. The parameter `level` is the level up the calling stack to look for the calling module and should not usually need to be set.""" mod_info = get_calling_module_info(module_name=calling_mod_name, module_path=calling_mod_path, level=level) calling_mod_name, calling_mod, calling_mod_path, calling_mod_dir = mod_info if calling_mod_name != "__main__": if not always_run: return # Convert string pytest_args arguments to a list. def convert_arg_string_to_list(arg_list_or_string): if not arg_list_or_string: pytest_arglist = [] elif isinstance(arg_list_or_string, str): pytest_arglist = ["-" + s for s in arg_list_or_string.split("-") if s] pytest_arglist = [s for x in pytest_arglist for s in x.split()] else: pytest_arglist = arg_list_or_string return pytest_arglist # Override arguments with any values set in the config file. pytest_arglist = convert_arg_string_to_list(pytest_args) pytest_arglist = convert_arg_string_to_list( # These override passed-in args. get_config_value("script_run_pytest_args", pytest_arglist, calling_mod, calling_mod_dir)) pytest_arglist += convert_arg_string_to_list( # These are added to passed-in args. get_config_value("script_run_extra_pytest_args", [], calling_mod, calling_mod_dir)) if modify_syspath: del sys.path[0] if isinstance(testfile_paths, str): testfile_paths = [testfile_paths] elif testfile_paths is None: testfile_paths = [] if self_test: testfile_paths.append(calling_mod_path) testfile_paths = [os.path.expanduser(p) for p in testfile_paths] # If pyargs is set, don't expand any arguments which do not have a slash in # them. In that case it was not a relative pathname anyway (except to # current dir, which necessitates the use of "./filename" rather than # "filename"). Will be treated as a package if pyargs=True. if pyargs: testfile_paths = [expand_relative(p, calling_mod_dir) if os.path.sep in p else p for p in testfile_paths] else: testfile_paths = [expand_relative(p, calling_mod_dir) for p in testfile_paths] # Add "--pyargs" to arguments if not there and no flag not to. if pyargs and "--pyargs" not in pytest_arglist: pytest_arglist.append("--pyargs") # Generate calling string and call pytest on the file. for t in testfile_paths: # Call pytest main; this requires pytest 2.0 or greater. py.test.main(pytest_arglist + [t]) if exit: sys.exit(0) return
def script_run(testfile_paths=None, self_test=False, pytest_args=None, pyargs=False, modify_syspath=None, calling_mod_name=None, calling_mod_path=None, single_call=True, exit=True, always_run=False, skip=False, pskip=False, level=2): """Run pytest on the specified test files when the calling module is run as a script. Using this function requires at least pytest 2.0. If the module from which this script is called is not `__main__` then this script immediately returns and does nothing (unless `always_run` is set true). The argument `testfile_paths` should be either the pathname of a file or directory to run pytest on, or else a list of such file and directory paths. Any relative paths will be interpreted relative to the directory of the module which calls this function. The calculation of relative paths can fail in cases where Python's CWD is changed between the time when the calling module is loaded and a pytest-helper function is called. (Most programs do not change the CWD like that, or if they do they return it to its previous value.) In those cases the `pytest_helper.init()` function can be called just after importing `pytest_helper` (or absolute pathnames can be used). The recommended use of `script_run` is to place it inside a guard conditional which runs only for scripts, and to call it before doing any other non-system imports. The early call avoids possible problems with relative imports when running it from inside modules that are part of packages. The use of the guard conditional is optional, but is more explicit and slightly more efficient. If `self_test` is `True` then pytest will be run on the file of the calling script itself, i.e., tests are assumed to be in the same file as the code to test. The `pytest_args` allows command-line arguments to be passed to pytest when it is run. It can be set to a string of all the options or else a list of strings (with one item in the list for each flag and for each flag argument). Options containing non-separator spaces, such as whitespace in quoted filenames, are currently not allowed in the string form. In that case the list form should be used. The pytest command-line argument `--pyargs` allows a mix of filenames and Python package names to be passed to pytest as test files. Note that pytest *always* imports modules as part of a package if there is an `__init__.py` file in the directory; the `--pyargs` just allows Python-style module names. When `pyargs` is set true pytest will be run with the `--pyargs` option set, and any items in `testfile_paths` which contain no path-separator character (slash) will be left unprocessed rather than being converted into absolute pathnames. The pytest option `--pyargs` will not work correctly unless this flag is set. The default is `pyargs=False`, i.e., by default all paths are converted to absolute pathnames. It usually will not matter, but in this mode you can specify a directory name relative to the current directory and not have it treated as a Python module name by using `./dirname` rather than simply `filename`. If `modify_syspath` is explicitly set `True` then the first item in the `sys.path` list is deleted, but only if it has not been deleted before (by this package or by the `set_package_attribute` package). If `modify_syspath` is set false then the system path is not modified. The default is `None`, which modifies the system path if the calling module is part of a package and otherwise does not. The reason for this option is that when a module is run as a script Python always adds the directory of the script as the first item in `sys.path`. This can sometimes cause hard-to-trace import errors when directories inside paths are inserted in `sys.path`. Deleting that added directory first prevents those errors. If `script_run` does not call exit at the end (because `exit==False`) then, before returning, any modified system path is restored to a saved copy of its full, original condition. The `calling_mod_name` argument is a fallback in case the calling function's module is not correctly located by introspection. It is usually not required (though it is slightly more efficient). Use it as: `module_name=__name__`. Similarly for `calling_mod_path`, but that should be passed the pathname of the calling module's file. By default the script-run program passes all the user-specified testfile paths to a single run of pytest. If `single_call` is set false then instead the paths are looped over, one by one, with a separate call to pytest on each one. If `exit` is set false `sys.exit(0)` will not be called after the tests finish. The default is to exit after the tests finish (otherwise when tests run from the top of a module are finished the rest of the file will still be executed). Setting `exit` false can be used to make several separate `script_run` calls in sequence. If `always_run` is true then tests will be run regardless of whether or not the function was called from a script. If `skip` is set to true from the default false then the function returns immediately without doing anything. This is just a keyword argument switch that can be used to temporarily turn off test-running without large changes to the code. The module runs normally without pytest being invoked. The `pskip` option is the same, except that it also sets the package attribute via `set_package_attribute`. This option is useful if the script is being run inside a package, since it allows relative imports to be used in the script. (In this case the `modify_syspath` argument is passed to the `init` function of `set_package_attribute`). The parameter `level` is the level up the calling stack to look for the calling module and should not usually need to be set.""" if skip: return if pskip: set_package_attribute.init(modify_syspath) return mod_info = get_calling_module_info(module_name=calling_mod_name, module_path=calling_mod_path, level=level) calling_mod_name, calling_mod, calling_mod_path, calling_mod_dir, in_pkg = mod_info if calling_mod_name != "__main__" and not always_run: return def convert_arg_string_to_list(arg_list_or_string): """Convert string pytest_args arguments to a list, keeping lists unchanged.""" if not arg_list_or_string: pytest_arglist = [] elif isinstance(arg_list_or_string, str): pytest_arglist = arg_list_or_string.split() else: pytest_arglist = arg_list_or_string return pytest_arglist # Override arguments with any values set in the config file. pytest_arglist = convert_arg_string_to_list(pytest_args) pytest_arglist = convert_arg_string_to_list( # These override passed-in args. get_config_value("script_run_pytest_args", pytest_arglist, calling_mod, calling_mod_dir)) pytest_arglist += convert_arg_string_to_list( # These are added to passed-in args. get_config_value("script_run_extra_pytest_args", [], calling_mod, calling_mod_dir)) if modify_syspath or (modify_syspath is None and in_pkg): set_package_attribute._delete_sys_path_0() if isinstance(testfile_paths, str): testfile_paths = [testfile_paths] elif testfile_paths is None: testfile_paths = [] if self_test: testfile_paths.append(calling_mod_path) testfile_paths = [os.path.expanduser(p) for p in testfile_paths] # If pyargs is set, don't expand any arguments which do not have a slash in # them. In that case it was not a relative pathname anyway (except to # current dir, which necessitates the use of "./filename" rather than # "filename"). Will be treated as a package if pyargs=True. if pyargs: testfile_paths = [ expand_relative(p, calling_mod_dir) if os.path.sep in p else p for p in testfile_paths ] else: testfile_paths = [ expand_relative(p, calling_mod_dir) for p in testfile_paths ] # Add "--pyargs" to arguments if not there and no flag not to. if pyargs and "--pyargs" not in pytest_arglist: pytest_arglist.append("--pyargs") # Generate calling string and call pytest on the file. if single_call: pytest.main(pytest_arglist + testfile_paths) else: for testfile in testfile_paths: # Call pytest main; this requires pytest 2.0 or greater. pytest.main(pytest_arglist + [testfile]) if exit: sys.exit(0)